├── .env ├── public ├── _redirects └── favicon.ico ├── env.d.ts ├── src ├── types │ ├── vue-apexcharts.d.ts │ ├── vue3-print-nb.d.ts │ ├── vue_tabler_icon.d.ts │ └── themeTypes │ │ └── ThemeType.ts ├── scss │ ├── components │ │ ├── _VShadow.scss │ │ ├── _VNavigationDrawer.scss │ │ ├── _VTabs.scss │ │ ├── _VTextField.scss │ │ ├── _VInput.scss │ │ ├── _VCard.scss │ │ ├── _VField.scss │ │ └── _VButtons.scss │ ├── style.scss │ ├── layout │ │ ├── _topbar.scss │ │ ├── _container.scss │ │ └── _sidebar.scss │ ├── _override.scss │ ├── _variables.scss │ └── pages │ │ └── _dashboards.scss ├── .vscode │ └── settings.json ├── assets │ └── images │ │ ├── cover │ │ ├── cover-1.webp │ │ ├── cover-2.webp │ │ ├── cover-3.webp │ │ ├── cover-4.webp │ │ ├── cover-5.webp │ │ ├── cover-6.webp │ │ ├── cover-7.webp │ │ ├── cover-8.webp │ │ ├── cover-9.webp │ │ ├── cover-10.webp │ │ ├── cover-11.webp │ │ ├── cover-12.webp │ │ ├── cover-13.webp │ │ ├── cover-14.webp │ │ ├── cover-15.webp │ │ ├── cover-16.webp │ │ ├── cover-17.webp │ │ ├── cover-18.webp │ │ ├── cover-19.webp │ │ ├── cover-20.webp │ │ ├── cover-21.webp │ │ ├── cover-22.webp │ │ ├── cover-23.webp │ │ ├── cover-24.webp │ │ ├── cover-25.webp │ │ └── cover-26.webp │ │ ├── logos │ │ ├── it-logo.png │ │ ├── it-logo-mid.png │ │ ├── it-logo-min.png │ │ ├── logo.svg │ │ └── logolight.svg │ │ ├── avatar │ │ ├── avatar-1.webp │ │ ├── avatar-2.webp │ │ ├── avatar-3.webp │ │ ├── avatar-4.webp │ │ ├── avatar-5.webp │ │ ├── avatar-6.webp │ │ ├── avatar-7.webp │ │ ├── avatar-8.webp │ │ ├── avatar-9.webp │ │ ├── avatar-10.webp │ │ ├── avatar-11.webp │ │ ├── avatar-12.webp │ │ ├── avatar-13.webp │ │ ├── avatar-14.webp │ │ ├── avatar-15.webp │ │ ├── avatar-16.webp │ │ ├── avatar-17.webp │ │ ├── avatar-18.webp │ │ ├── avatar-19.webp │ │ ├── avatar-20.webp │ │ ├── avatar-21.webp │ │ ├── avatar-22.webp │ │ ├── avatar-23.webp │ │ ├── avatar-24.webp │ │ └── avatar-25.webp │ │ ├── customer │ │ ├── avatar-0.webp │ │ ├── avatar-1.png │ │ ├── avatar-10.png │ │ ├── avatar-11.png │ │ ├── avatar-12.png │ │ ├── avatar-2.png │ │ ├── avatar-3.png │ │ ├── avatar-4.png │ │ ├── avatar-5.png │ │ ├── avatar-6.png │ │ ├── avatar-7.png │ │ ├── avatar-8.png │ │ └── avatar-9.png │ │ ├── product │ │ ├── avatar-0.webp │ │ ├── product-0.webp │ │ ├── product-1.webp │ │ ├── product-10.webp │ │ ├── product-11.webp │ │ ├── product-12.webp │ │ ├── product-13.webp │ │ ├── product-14.webp │ │ ├── product-15.webp │ │ ├── product-16.webp │ │ ├── product-17.webp │ │ ├── product-18.webp │ │ ├── product-19.webp │ │ ├── product-2.webp │ │ ├── product-20.webp │ │ ├── product-21.webp │ │ ├── product-22.webp │ │ ├── product-23.webp │ │ ├── product-24.webp │ │ ├── product-25.webp │ │ ├── product-26.webp │ │ ├── product-27.webp │ │ ├── product-28.webp │ │ ├── product-29.webp │ │ ├── product-3.webp │ │ ├── product-30.webp │ │ ├── product-31.webp │ │ ├── product-4.webp │ │ ├── product-5.webp │ │ ├── product-6.webp │ │ ├── product-7.webp │ │ ├── product-8.webp │ │ └── product-9.webp │ │ ├── maintenance │ │ ├── coming-soon.webp │ │ ├── img-error-text.svg │ │ ├── img-error-purple.svg │ │ └── img-error-bg.svg │ │ ├── icons │ │ └── icon-card.svg │ │ └── auth │ │ └── social-google.svg ├── App.vue ├── layouts │ ├── full │ │ ├── vertical-sidebar │ │ │ ├── NavGroup │ │ │ │ └── NavGroup.vue │ │ │ ├── IconSet.vue │ │ │ ├── NavItem │ │ │ │ └── NavItem.vue │ │ │ ├── NavCollapse │ │ │ │ └── NavCollapse.vue │ │ │ ├── VerticalSidebar.vue │ │ │ └── sidebarItem.ts │ │ ├── logo │ │ │ ├── LogoMain.vue │ │ │ └── Logo.vue │ │ ├── footer │ │ │ └── FooterPanel.vue │ │ ├── vertical-header │ │ │ ├── SearchBarPanel.vue │ │ │ ├── ProfileDD.vue │ │ │ └── VerticalHeader.vue │ │ ├── FullLayout.vue │ │ └── customizer │ │ │ └── CustomizerPanel.vue │ └── blank │ │ └── BlankLayout.vue ├── components │ └── shared │ │ ├── UiMainContainer.vue │ │ ├── UiChildCard.vue │ │ ├── UiTitleCard.vue │ │ ├── NotificeBar.vue │ │ ├── ConfirmDialog.vue │ │ ├── UiParentCard.vue │ │ └── BaseBreadcrumb.vue ├── config.ts ├── stores │ ├── counter.ts │ ├── authUser.ts │ ├── customizer.ts │ ├── customers.ts │ ├── blogs.ts │ ├── orders.ts │ ├── auth.ts │ └── products.ts ├── views │ ├── notfound │ │ ├── Loading.vue │ │ └── NotFound.vue │ ├── pages │ │ ├── maintenance │ │ │ ├── comingsoon │ │ │ │ └── ComingSoon.vue │ │ │ └── error │ │ │ │ └── Error404Page.vue │ │ └── CustomerView.vue │ ├── utilities │ │ ├── icons │ │ │ ├── TablerIcons.vue │ │ │ └── MaterialIcons.vue │ │ ├── shadows │ │ │ └── ShadowPage.vue │ │ ├── colors │ │ │ └── ColorPage.vue │ │ └── typography │ │ │ └── TypographyPage.vue │ ├── StarterPage.vue │ ├── dashboards │ │ ├── components │ │ │ ├── TotalIncome.vue │ │ │ ├── HelpSupport.vue │ │ │ ├── IncomeOverview.vue │ │ │ ├── TotalEarning.vue │ │ │ ├── TransactionHistory.vue │ │ │ ├── WidgetFive.vue │ │ │ ├── AnalyticsReport.vue │ │ │ ├── TotalGrowth.vue │ │ │ ├── RecentOrder.vue │ │ │ ├── UniqueVisitor.vue │ │ │ ├── TotalOrder.vue │ │ │ └── SalesReport.vue │ │ └── Dashboard.vue │ └── authentication │ │ ├── auth │ │ ├── LoginPage.vue │ │ └── RegisterPage.vue │ │ └── authForms │ │ ├── AuthLogin.vue │ │ └── AuthRegister.vue ├── utils │ ├── locales │ │ └── format.ts │ └── helpers │ │ └── fetch-wrapper.ts ├── plugins │ ├── mdi-icon.ts │ └── vuetify.ts ├── main.ts ├── router │ ├── PublicRoutes.ts │ ├── index.ts │ └── MainRoutes.ts ├── theme │ └── LightTheme.ts └── types.d.ts ├── config ├── prod.env.js ├── test.env.js ├── dev.env.js ├── nginx.conf └── index.js ├── screenshots ├── screenshot-1.jpg ├── screenshot-2.jpg ├── screenshot-2.png ├── screenshot-3.jpg ├── screenshot-3.png ├── screenshot-4.jpg ├── screenshot-4.png ├── screenshot-5.jpg ├── screenshot-6.jpg └── v3 │ ├── Screenshot-1.png │ ├── Screenshot-2.png │ ├── Screenshot-3.png │ ├── Screenshot-4.png │ ├── Screenshot-5.png │ ├── Screenshot-6.png │ └── Screenshot-7.png ├── .prettierrc ├── tsconfig.vite-config.json ├── tsconfig.json ├── eslint.config.js ├── Dockerfile ├── index.html ├── .gitignore ├── vite.config.ts ├── package.json └── README.md /.env: -------------------------------------------------------------------------------- 1 | API_URL="http://localhost" -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/types/vue-apexcharts.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue3-apexcharts'; 2 | -------------------------------------------------------------------------------- /src/types/vue3-print-nb.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue3-print-nb'; 2 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/scss/components/_VShadow.scss: -------------------------------------------------------------------------------- 1 | .elevation-10 { 2 | box-shadow: $box-shadow !important; 3 | } 4 | -------------------------------------------------------------------------------- /screenshots/screenshot-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/screenshots/screenshot-1.jpg -------------------------------------------------------------------------------- /screenshots/screenshot-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/screenshots/screenshot-2.jpg -------------------------------------------------------------------------------- /screenshots/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/screenshots/screenshot-2.png -------------------------------------------------------------------------------- /screenshots/screenshot-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/screenshots/screenshot-3.jpg -------------------------------------------------------------------------------- /screenshots/screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/screenshots/screenshot-3.png -------------------------------------------------------------------------------- /screenshots/screenshot-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/screenshots/screenshot-4.jpg -------------------------------------------------------------------------------- /screenshots/screenshot-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/screenshots/screenshot-4.png -------------------------------------------------------------------------------- /screenshots/screenshot-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/screenshots/screenshot-5.jpg -------------------------------------------------------------------------------- /screenshots/screenshot-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/screenshots/screenshot-6.jpg -------------------------------------------------------------------------------- /src/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vue3snippets.enable-compile-vue-file-on-did-save-code": false 3 | } 4 | -------------------------------------------------------------------------------- /screenshots/v3/Screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/screenshots/v3/Screenshot-1.png -------------------------------------------------------------------------------- /screenshots/v3/Screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/screenshots/v3/Screenshot-2.png -------------------------------------------------------------------------------- /screenshots/v3/Screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/screenshots/v3/Screenshot-3.png -------------------------------------------------------------------------------- /screenshots/v3/Screenshot-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/screenshots/v3/Screenshot-4.png -------------------------------------------------------------------------------- /screenshots/v3/Screenshot-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/screenshots/v3/Screenshot-5.png -------------------------------------------------------------------------------- /screenshots/v3/Screenshot-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/screenshots/v3/Screenshot-6.png -------------------------------------------------------------------------------- /screenshots/v3/Screenshot-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/screenshots/v3/Screenshot-7.png -------------------------------------------------------------------------------- /src/assets/images/cover/cover-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-1.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-2.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-3.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-4.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-5.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-6.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-7.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-7.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-8.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-8.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-9.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-9.webp -------------------------------------------------------------------------------- /src/assets/images/logos/it-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/logos/it-logo.png -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-1.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-2.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-3.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-4.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-5.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-6.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-7.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-7.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-8.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-8.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-9.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-9.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-10.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-10.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-11.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-11.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-12.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-12.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-13.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-13.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-14.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-14.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-15.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-15.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-16.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-16.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-17.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-17.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-18.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-18.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-19.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-19.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-20.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-20.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-21.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-21.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-22.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-22.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-23.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-23.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-24.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-24.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-25.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-25.webp -------------------------------------------------------------------------------- /src/assets/images/cover/cover-26.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/cover/cover-26.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-10.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-10.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-11.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-11.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-12.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-12.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-13.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-13.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-14.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-14.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-15.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-15.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-16.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-16.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-17.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-17.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-18.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-18.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-19.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-19.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-20.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-20.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-21.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-21.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-22.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-22.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-23.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-23.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-24.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-24.webp -------------------------------------------------------------------------------- /src/assets/images/avatar/avatar-25.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/avatar/avatar-25.webp -------------------------------------------------------------------------------- /src/assets/images/customer/avatar-0.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/customer/avatar-0.webp -------------------------------------------------------------------------------- /src/assets/images/customer/avatar-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/customer/avatar-1.png -------------------------------------------------------------------------------- /src/assets/images/customer/avatar-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/customer/avatar-10.png -------------------------------------------------------------------------------- /src/assets/images/customer/avatar-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/customer/avatar-11.png -------------------------------------------------------------------------------- /src/assets/images/customer/avatar-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/customer/avatar-12.png -------------------------------------------------------------------------------- /src/assets/images/customer/avatar-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/customer/avatar-2.png -------------------------------------------------------------------------------- /src/assets/images/customer/avatar-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/customer/avatar-3.png -------------------------------------------------------------------------------- /src/assets/images/customer/avatar-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/customer/avatar-4.png -------------------------------------------------------------------------------- /src/assets/images/customer/avatar-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/customer/avatar-5.png -------------------------------------------------------------------------------- /src/assets/images/customer/avatar-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/customer/avatar-6.png -------------------------------------------------------------------------------- /src/assets/images/customer/avatar-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/customer/avatar-7.png -------------------------------------------------------------------------------- /src/assets/images/customer/avatar-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/customer/avatar-8.png -------------------------------------------------------------------------------- /src/assets/images/customer/avatar-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/customer/avatar-9.png -------------------------------------------------------------------------------- /src/assets/images/logos/it-logo-mid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/logos/it-logo-mid.png -------------------------------------------------------------------------------- /src/assets/images/logos/it-logo-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/logos/it-logo-min.png -------------------------------------------------------------------------------- /src/assets/images/product/avatar-0.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/avatar-0.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-0.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-0.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-1.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-10.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-10.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-11.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-11.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-12.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-12.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-13.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-13.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-14.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-14.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-15.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-15.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-16.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-16.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-17.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-17.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-18.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-18.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-19.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-19.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-2.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-20.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-20.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-21.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-21.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-22.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-22.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-23.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-23.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-24.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-24.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-25.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-25.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-26.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-26.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-27.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-27.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-28.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-28.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-29.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-29.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-3.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-30.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-30.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-31.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-31.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-4.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-5.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-6.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-7.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-7.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-8.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-8.webp -------------------------------------------------------------------------------- /src/assets/images/product/product-9.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/product/product-9.webp -------------------------------------------------------------------------------- /src/scss/components/_VNavigationDrawer.scss: -------------------------------------------------------------------------------- 1 | .v-navigation-drawer__scrim.fade-transition-leave-to { 2 | display: none; 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/images/maintenance/coming-soon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryho/vue-crm/HEAD/src/assets/images/maintenance/coming-soon.webp -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var devEnv = require('./dev.env') 3 | 4 | module.exports = merge(devEnv, { 5 | NODE_ENV: '"testing"' 6 | }) 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "printWidth": 140, 4 | "singleQuote": true, 5 | "trailingComma": "none", 6 | "tabWidth": 2, 7 | "useTabs": false 8 | } 9 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /tsconfig.vite-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node22/tsconfig.json", 3 | "include": ["vite.config.*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "allowJs": true, 7 | "types": ["node"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/layouts/full/vertical-sidebar/NavGroup/NavGroup.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /src/layouts/blank/BlankLayout.vue: -------------------------------------------------------------------------------- 1 | // ===============================|| Blank Layout ||=============================== // 2 | 7 | 10 | -------------------------------------------------------------------------------- /src/scss/components/_VTabs.scss: -------------------------------------------------------------------------------- 1 | .theme-tab { 2 | &.v-tabs { 3 | .v-tab { 4 | border-radius: $border-radius-root !important; 5 | min-width: auto !important; 6 | &.v-tab-item--selected { 7 | background: rgb(var(--v-theme-primary)); 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/types/vue_tabler_icon.d.ts: -------------------------------------------------------------------------------- 1 | import { VNodeChild } from 'vue'; 2 | declare module '@vue/runtime-dom' { 3 | export interface HTMLAttributes { 4 | $children?: VNodeChild; 5 | } 6 | export interface SVGAttributes { 7 | $children?: VNodeChild; 8 | strokeWidth?: string | number; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/shared/UiMainContainer.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | // ===============================|| Ui Main Container||=============================== // 5 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | export type ConfigProps = { 2 | Sidebar_drawer: boolean; 3 | Customizer_drawer: boolean; 4 | mini_sidebar: boolean; 5 | fontTheme: string; 6 | inputBg: boolean; 7 | }; 8 | 9 | const config: ConfigProps = { 10 | Sidebar_drawer: true, 11 | Customizer_drawer: false, 12 | mini_sidebar: false, 13 | fontTheme: 'Roboto', 14 | inputBg: false 15 | }; 16 | 17 | export default config; 18 | -------------------------------------------------------------------------------- /src/stores/counter.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | export const useCounterStore = defineStore({ 4 | id: 'counter', 5 | state: () => ({ 6 | counter: 0 7 | }), 8 | getters: { 9 | doubleCount: (state) => state.counter * 2 10 | }, 11 | actions: { 12 | increment() { 13 | this.counter++; 14 | }, 15 | decrement() { 16 | this.counter--; 17 | } 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /src/scss/components/_VTextField.scss: -------------------------------------------------------------------------------- 1 | .v-text-field input { 2 | font-size: 0.875rem; 3 | } 4 | .v-input--density-default { 5 | .v-field__input { 6 | min-height: 51px; 7 | } 8 | } 9 | 10 | .v-field__outline { 11 | color: rgb(var(--v-theme-inputBorder)); 12 | } 13 | .inputWithbg { 14 | .v-field--variant-outlined { 15 | background-color: rgba(0, 0, 0, 0.025); 16 | border-radius: 12px !important; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/scss/components/_VInput.scss: -------------------------------------------------------------------------------- 1 | .v-input--density-default, 2 | .v-field--variant-solo, 3 | .v-field--variant-filled { 4 | --v-input-control-height: 51px; 5 | --v-input-padding-top: 14px; 6 | } 7 | .v-input--density-comfortable { 8 | --v-input-control-height: 56px; 9 | --v-input-padding-top: 17px; 10 | } 11 | .v-label { 12 | font-size: 0.975rem; 13 | } 14 | .v-switch .v-label, 15 | .v-checkbox .v-label { 16 | opacity: 1; 17 | } 18 | -------------------------------------------------------------------------------- /src/components/shared/UiChildCard.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | -------------------------------------------------------------------------------- /src/scss/components/_VCard.scss: -------------------------------------------------------------------------------- 1 | // Outline Card 2 | .v-card--variant-outlined { 3 | border-color: rgba(var(--v-theme-borderLight), 0.36); 4 | .v-divider { 5 | border-color: rgba(var(--v-theme-borderLight), 0.36); 6 | } 7 | } 8 | 9 | .v-card-text { 10 | padding: $card-text-spacer; 11 | } 12 | 13 | .v-card { 14 | width: 100%; 15 | overflow: visible; 16 | } 17 | 18 | .v-card-item { 19 | padding: $card-item-spacer-xy; 20 | } 21 | -------------------------------------------------------------------------------- /src/scss/components/_VField.scss: -------------------------------------------------------------------------------- 1 | .v-field--variant-outlined .v-field__outline__start.v-locale--is-ltr, 2 | .v-locale--is-ltr .v-field--variant-outlined .v-field__outline__start { 3 | border-radius: $border-radius-root 0 0 $border-radius-root; 4 | } 5 | 6 | .v-field--variant-outlined .v-field__outline__end.v-locale--is-ltr, 7 | .v-locale--is-ltr .v-field--variant-outlined .v-field__outline__end { 8 | border-radius: 0 $border-radius-root $border-radius-root 0; 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "src/types/.d.ts"], 4 | "compilerOptions": { 5 | "module": "ESNext", 6 | "ignoreDeprecations": "5.0", 7 | "baseUrl": ".", 8 | "paths": { 9 | "@/*": ["./src/*"] 10 | }, 11 | "allowJs": true 12 | }, 13 | 14 | "references": [ 15 | { 16 | "path": "./tsconfig.vite-config.json" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/layouts/full/vertical-sidebar/IconSet.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import globals from 'globals'; 2 | import pluginJs from '@eslint/js'; 3 | import tseslint from 'typescript-eslint'; 4 | import pluginVue from 'eslint-plugin-vue'; 5 | 6 | export default [ 7 | { 8 | languageOptions: { 9 | globals: globals.browser, 10 | parserOptions: { 11 | parser: '@typescript-eslint/parser' 12 | } 13 | } 14 | }, 15 | pluginJs.configs.recommended, 16 | ...tseslint.configs.recommended, 17 | ...pluginVue.configs['flat/essential'] 18 | ]; 19 | -------------------------------------------------------------------------------- /src/layouts/full/logo/LogoMain.vue: -------------------------------------------------------------------------------- 1 | 7 | 19 | 20 | -------------------------------------------------------------------------------- /src/components/shared/UiTitleCard.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | -------------------------------------------------------------------------------- /src/scss/components/_VButtons.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Light Buttons 3 | // 4 | 5 | .v-btn { 6 | &.bg-lightsecondary { 7 | &:hover, 8 | &:active, 9 | &:focus { 10 | background-color: rgb(var(--v-theme-secondary)) !important; 11 | color: $white !important; 12 | } 13 | } 14 | } 15 | 16 | .v-btn { 17 | text-transform: capitalize; 18 | letter-spacing: $btn-letter-spacing; 19 | } 20 | .v-btn--icon.v-btn--density-default { 21 | width: calc(var(--v-btn-height) + 6px); 22 | height: calc(var(--v-btn-height) + 6px); 23 | } 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ###### Build ##### 2 | FROM node:12-slim AS node 3 | LABEL author="Harry Ho" 4 | WORKDIR / 5 | COPY . . 6 | RUN npm install 7 | RUN npm run build -- --prod 8 | 9 | 10 | ###### Build the Delivery ##### 11 | FROM nginx:alpine 12 | LABEL author="Harry Ho" 13 | WORKDIR /var/cache/nginx 14 | COPY --from=node /dist /usr/share/nginx/html 15 | COPY ./config/nginx.conf /etc/nginx/conf.d/default.conf 16 | 17 | 18 | # ######################## 19 | # # docker build . -t vue-demo:3.0 20 | # # docker run --rm -d --publish 8080:80 --name vd3 vue-demo:3.0 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/views/notfound/Loading.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | -------------------------------------------------------------------------------- /src/stores/authUser.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | import { fetchWrapper } from '@/utils/helpers/fetch-wrapper'; 4 | 5 | const baseUrl = `${import.meta.env.VITE_API_URL}/users`; 6 | 7 | export const useUsersStore = defineStore({ 8 | id: 'Customers', 9 | state: () => ({ 10 | users: {} 11 | }), 12 | actions: { 13 | async getAll() { 14 | this.users = { loading: true }; 15 | fetchWrapper 16 | .get(baseUrl) 17 | .then((users) => (this.users = users)) 18 | .catch((error) => (this.users = { error })); 19 | } 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /src/scss/style.scss: -------------------------------------------------------------------------------- 1 | @import './variables'; 2 | @import 'vuetify/styles/main.sass'; 3 | @import './override'; 4 | @import './layout/container'; 5 | @import './layout/sidebar'; 6 | @import './layout/topbar'; 7 | 8 | @import './components/VButtons'; 9 | @import './components/VCard'; 10 | @import './components/VField'; 11 | @import './components/VInput'; 12 | @import './components/VNavigationDrawer'; 13 | @import './components/VShadow'; 14 | @import './components/VTextField'; 15 | @import './components/VTabs'; 16 | 17 | @import './pages/dashboards'; 18 | 19 | @import 'vue3-perfect-scrollbar/style.css'; 20 | -------------------------------------------------------------------------------- /src/layouts/full/logo/Logo.vue: -------------------------------------------------------------------------------- 1 | 13 | 19 | -------------------------------------------------------------------------------- /config/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 0.0.0.0:80; 3 | listen [::]:80; 4 | default_type application/octet-stream; 5 | 6 | gzip on; 7 | gzip_comp_level 6; 8 | gzip_vary on; 9 | gzip_min_length 1000; 10 | gzip_proxied any; 11 | gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; 12 | gzip_buffers 16 8k; 13 | client_max_body_size 256M; 14 | 15 | root /usr/share/nginx/html; 16 | 17 | location / { 18 | try_files $uri $uri/ /index.html =404; 19 | } 20 | } -------------------------------------------------------------------------------- /src/layouts/full/footer/FooterPanel.vue: -------------------------------------------------------------------------------- 1 | 6 | 17 | 18 | 30 | -------------------------------------------------------------------------------- /src/utils/locales/format.ts: -------------------------------------------------------------------------------- 1 | export function formatToCurrencyString(price: number):string{ 2 | 3 | return new Intl.NumberFormat('en-AU', { 4 | style: 'currency', 5 | currency: 'AUD', 6 | minimumFractionDigits: 2, 7 | maximumFractionDigits: 2 8 | }).format(price) 9 | } 10 | 11 | export function formatToCurrencyNumber(price: number):number { 12 | 13 | return Number(price.toFixed(2)) 14 | } 15 | 16 | 17 | export function toTitleCase(str:string) :string{ 18 | str = str.replace('-',' ').replace(',',' ') 19 | return str.split(' ').map(word => { 20 | return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); 21 | }).join(' '); 22 | } -------------------------------------------------------------------------------- /src/plugins/mdi-icon.ts: -------------------------------------------------------------------------------- 1 | import { 2 | mdiClose, mdiHome, mdiEyeOff, mdiEye, 3 | mdiCrownCircleOutline, mdiCertificateOutline, 4 | mdiShoppingOutline, mdiPencilCircleOutline, mdiDeleteCircleOutline, 5 | mdiPlusCircleOutline, mdiCommentMultiple, mdiShareVariant, mdiGithub 6 | } from '@mdi/js'; 7 | 8 | export const icons = { 9 | close: mdiClose, 10 | home: mdiHome, 11 | eyeOff: mdiEyeOff, 12 | eye: mdiEye, 13 | vip: mdiCrownCircleOutline, 14 | standard: mdiCertificateOutline, 15 | pendingShopping: mdiShoppingOutline, 16 | edit: mdiPencilCircleOutline, 17 | delete: mdiDeleteCircleOutline, 18 | new: mdiPlusCircleOutline, 19 | share: mdiShareVariant, 20 | comments: mdiCommentMultiple, 21 | github: mdiGithub 22 | }; 23 | -------------------------------------------------------------------------------- /src/components/shared/NotificeBar.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | -------------------------------------------------------------------------------- /src/views/pages/maintenance/comingsoon/ComingSoon.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 22 | 28 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 15 | Vue Demo V3 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/assets/images/icons/icon-card.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import { createPinia } from 'pinia'; 3 | import App from './App.vue'; 4 | import { router } from './router'; 5 | import vuetify from './plugins/vuetify'; 6 | import { VDateInput } from 'vuetify/labs/components'; 7 | import '@/scss/style.scss'; 8 | import { PerfectScrollbarPlugin } from 'vue3-perfect-scrollbar'; 9 | import VueApexCharts from 'vue3-apexcharts'; 10 | import VueTablerIcons from 'vue-tabler-icons'; 11 | 12 | import { fakeBackend } from '@/utils/helpers/fake-backend'; 13 | 14 | // print 15 | import print from 'vue3-print-nb'; 16 | 17 | const app = createApp(App); 18 | fakeBackend(); 19 | app.use(router); 20 | app.use(PerfectScrollbarPlugin); 21 | app.use(createPinia()); 22 | app.use(VueTablerIcons); 23 | app.use(print); 24 | app.use(VueApexCharts); 25 | app.use(vuetify).mount('#app'); 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # These are some examples of commonly ignored file patterns. 2 | # You should customize this list as applicable to your project. 3 | # Learn more about .gitignore: 4 | # https://www.atlassian.com/git/tutorials/saving-changes/gitignore 5 | 6 | # Node artifact files 7 | node_modules/ 8 | dist/ 9 | 10 | # Compiled Java class files 11 | *.class 12 | 13 | # Compiled Python bytecode 14 | *.py[cod] 15 | 16 | # Log files 17 | *.log 18 | 19 | # Package files 20 | *.jar 21 | 22 | # Maven 23 | target/ 24 | dist/ 25 | 26 | # JetBrains IDE 27 | .idea/ 28 | 29 | # Unit test reports 30 | TEST*.xml 31 | 32 | # Generated by MacOS 33 | .DS_Store 34 | 35 | # Generated by Windows 36 | Thumbs.db 37 | 38 | # Applications 39 | *.app 40 | *.exe 41 | *.war 42 | 43 | # Large media files 44 | *.mp4 45 | *.tiff 46 | *.avi 47 | *.flv 48 | *.mov 49 | *.wmv 50 | 51 | -------------------------------------------------------------------------------- /src/router/PublicRoutes.ts: -------------------------------------------------------------------------------- 1 | const PublicRoutes = { 2 | path: '/', 3 | component: () => import('@/layouts/blank/BlankLayout.vue'), 4 | meta: { 5 | requiresAuth: false 6 | }, 7 | children: [ 8 | { 9 | name: 'Login', 10 | path: '/login', 11 | component: () => import('@/views/authentication/auth/LoginPage.vue') 12 | }, 13 | { 14 | name: 'Register', 15 | path: '/register', 16 | component: () => import('@/views/authentication/auth/RegisterPage.vue') 17 | }, 18 | { 19 | name: 'Not Found', 20 | path: '/notfound', 21 | component: () => import('@/views/notfound/NotFound.vue') 22 | }, 23 | { 24 | name: 'Loading', 25 | path: '/loading', 26 | component: () => import('@/views/notfound/Loading.vue') 27 | } 28 | ] 29 | }; 30 | 31 | export default PublicRoutes; 32 | -------------------------------------------------------------------------------- /src/stores/customizer.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import config from '@/config'; 3 | 4 | export const useCustomizerStore = defineStore({ 5 | id: 'customizer', 6 | state: () => ({ 7 | Sidebar_drawer: config.Sidebar_drawer, 8 | Customizer_drawer: config.Customizer_drawer, 9 | mini_sidebar: config.mini_sidebar, 10 | fontTheme: config.fontTheme, 11 | inputBg: config.inputBg 12 | }), 13 | 14 | getters: {}, 15 | actions: { 16 | SET_SIDEBAR_DRAWER() { 17 | this.Sidebar_drawer = !this.Sidebar_drawer; 18 | }, 19 | SET_MINI_SIDEBAR(payload: boolean) { 20 | this.mini_sidebar = payload; 21 | }, 22 | SET_CUSTOMIZER_DRAWER(payload: boolean) { 23 | this.Customizer_drawer = payload; 24 | }, 25 | SET_FONT(payload: string) { 26 | this.fontTheme = payload; 27 | } 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /src/scss/layout/_topbar.scss: -------------------------------------------------------------------------------- 1 | .profileBtn { 2 | height: 50px !important; 3 | margin: 0 20px 0 10px !important; 4 | } 5 | 6 | .search-sheet { 7 | position: absolute; 8 | z-index: 9; 9 | } 10 | 11 | .circle { 12 | position: relative; 13 | overflow: hidden; 14 | &.sm-circle { 15 | &::before { 16 | content: ''; 17 | position: absolute; 18 | width: 200px; 19 | height: 200px; 20 | border: 3px solid rgb(var(--v-theme-warning)); 21 | border-radius: 50%; 22 | top: 125px; 23 | right: -70px; 24 | } 25 | } 26 | 27 | &.lg-circle { 28 | &::after { 29 | content: ''; 30 | position: absolute; 31 | width: 200px; 32 | height: 200px; 33 | border: 19px solid rgb(var(--v-theme-warning)); 34 | border-radius: 50%; 35 | top: 65px; 36 | right: -150px; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/views/utilities/icons/TablerIcons.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 32 | -------------------------------------------------------------------------------- /src/types/themeTypes/ThemeType.ts: -------------------------------------------------------------------------------- 1 | export type ThemeTypes = { 2 | name: string; 3 | dark: boolean; 4 | variables?: object; 5 | colors: { 6 | primary?: string; 7 | secondary?: string; 8 | info?: string; 9 | success?: string; 10 | accent?: string; 11 | warning?: string; 12 | error?: string; 13 | lightprimary?: string; 14 | lightsecondary?: string; 15 | lightsuccess?: string; 16 | lighterror?: string; 17 | lightwarning?: string; 18 | darkprimary?: string; 19 | darksecondary?: string; 20 | darkText?: string; 21 | lightText?: string; 22 | borderLight?: string; 23 | inputBorder?: string; 24 | containerBg?: string; 25 | surface?: string; 26 | background?: string; 27 | 'on-surface-variant'?: string; 28 | facebook?: string; 29 | twitter?: string; 30 | linkedin?: string; 31 | gray100?: string; 32 | primary200?: string; 33 | secondary200?: string; 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /src/views/utilities/icons/MaterialIcons.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 32 | -------------------------------------------------------------------------------- /src/components/shared/ConfirmDialog.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /src/components/shared/UiParentCard.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | // ===============================|| Ui Parent Card||=============================== // 16 | 36 | -------------------------------------------------------------------------------- /src/plugins/vuetify.ts: -------------------------------------------------------------------------------- 1 | import { createVuetify } from 'vuetify'; 2 | import { aliases, mdi } from 'vuetify/iconsets/mdi-svg'; 3 | import { icons } from './mdi-icon'; // Import icons from separate file 4 | import * as components from 'vuetify/components'; 5 | import * as directives from 'vuetify/directives'; 6 | import { PurpleTheme } from '@/theme/LightTheme'; 7 | import * as labsComponents from 'vuetify/labs/components' 8 | 9 | export default createVuetify({ 10 | components:{ 11 | ...components, 12 | ...labsComponents, 13 | }, 14 | directives, 15 | icons: { 16 | defaultSet: 'mdi', 17 | aliases: { 18 | ...aliases, 19 | ...icons 20 | }, 21 | sets: { 22 | mdi 23 | } 24 | }, 25 | theme: { 26 | defaultTheme: 'PurpleTheme', 27 | themes: { 28 | PurpleTheme 29 | } 30 | }, 31 | defaults: { 32 | VBtn: {}, 33 | VCard: { 34 | rounded: 'md' 35 | }, 36 | VTextField: { 37 | rounded: 'lg' 38 | }, 39 | VTooltip: { 40 | // set v-tooltip default location to top 41 | location: 'top' 42 | } 43 | } 44 | }); 45 | -------------------------------------------------------------------------------- /src/views/utilities/shadows/ShadowPage.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 37 | -------------------------------------------------------------------------------- /src/views/notfound/NotFound.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 23 | 44 | -------------------------------------------------------------------------------- /src/theme/LightTheme.ts: -------------------------------------------------------------------------------- 1 | import type { ThemeTypes } from '@/types/themeTypes/ThemeType'; 2 | 3 | const PurpleTheme: ThemeTypes = { 4 | name: 'PurpleTheme', 5 | dark: false, 6 | variables: { 7 | 'border-color': '#1e88e5', 8 | 'carousel-control-size': 10 9 | }, 10 | colors: { 11 | primary: '#1e88e5', 12 | secondary: '#5e35b1', 13 | info: '#03c9d7', 14 | success: '#00c853', 15 | accent: '#FFAB91', 16 | warning: '#ffc107', 17 | error: '#f44336', 18 | lightprimary: '#eef2f6', 19 | lightsecondary: '#ede7f6', 20 | lightsuccess: '#b9f6ca', 21 | lighterror: '#f9d8d8', 22 | lightwarning: '#fff8e1', 23 | darkText: '#212121', 24 | lightText: '#616161', 25 | darkprimary: '#1565c0', 26 | darksecondary: '#4527a0', 27 | borderLight: '#d0d0d0', 28 | inputBorder: '#787878', 29 | containerBg: '#eef2f6', 30 | surface: '#fff', 31 | 'on-surface-variant': '#fff', 32 | facebook: '#4267b2', 33 | twitter: '#1da1f2', 34 | linkedin: '#0e76a8', 35 | gray100: '#fafafa', 36 | primary200: '#90caf9', 37 | secondary200: '#b39ddb' 38 | } 39 | }; 40 | 41 | export { PurpleTheme }; 42 | -------------------------------------------------------------------------------- /src/views/StarterPage.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 35 | -------------------------------------------------------------------------------- /src/assets/images/auth/social-google.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'url'; 2 | import { defineConfig,normalizePath } from 'vite'; 3 | import vue from '@vitejs/plugin-vue'; 4 | import vuetify from 'vite-plugin-vuetify'; 5 | import { viteStaticCopy } from 'vite-plugin-static-copy' 6 | import path from 'node:path' 7 | 8 | // https://vitejs.dev/config/ 9 | export default defineConfig({ 10 | plugins: [ 11 | viteStaticCopy({ 12 | targets: [ 13 | { 14 | src: [ normalizePath(path.resolve(__dirname, './src/assets/images'))+ '/[!.]*', ], 15 | dest: './assets/images' 16 | } 17 | ] 18 | }), 19 | vue({ 20 | template: { 21 | compilerOptions: { 22 | isCustomElement: (tag) => ['v-list-recognize-title'].includes(tag) 23 | } 24 | } 25 | }), 26 | vuetify({ 27 | autoImport: true 28 | }) 29 | ], 30 | resolve: { 31 | alias: { 32 | '@': fileURLToPath(new URL('./src', import.meta.url)) 33 | } 34 | }, 35 | css: { 36 | preprocessorOptions: { 37 | scss: {} 38 | } 39 | }, 40 | assetsInclude:[''], 41 | build: { 42 | chunkSizeWarningLimit: 1024 * 1024 // Set the limit to 1 MB 43 | }, 44 | optimizeDeps: { 45 | exclude: ['vuetify'], 46 | entries: ['./src/**/*.vue'] 47 | } 48 | }); 49 | -------------------------------------------------------------------------------- /src/views/dashboards/components/TotalIncome.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 32 | -------------------------------------------------------------------------------- /src/layouts/full/vertical-header/SearchBarPanel.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 38 | -------------------------------------------------------------------------------- /src/scss/_override.scss: -------------------------------------------------------------------------------- 1 | html { 2 | .bg-success { 3 | color: white !important; 4 | } 5 | } 6 | 7 | .v-row + .v-row { 8 | margin-top: 0px; 9 | } 10 | 11 | .v-divider { 12 | opacity: 1; 13 | border-color: rgba(var(--v-theme-borderLight), 0.36); 14 | } 15 | 16 | .v-selection-control { 17 | flex: unset; 18 | } 19 | 20 | .customizer-btn .icon { 21 | animation: progress-circular-rotate 1.4s linear infinite; 22 | transform-origin: center center; 23 | transition: all 0.2s ease-in-out; 24 | } 25 | 26 | .no-spacer { 27 | .v-list-item__spacer { 28 | display: none !important; 29 | } 30 | } 31 | 32 | @keyframes progress-circular-rotate { 33 | 100% { 34 | transform: rotate(270deg); 35 | } 36 | } 37 | 38 | // apexchart css 39 | .apexcharts-canvas { 40 | .apexcharts-tooltip-rangebar { 41 | padding: 0px 8px 5px; 42 | } 43 | .apexcharts-tooltip-title { 44 | margin-bottom: 0; 45 | } 46 | .apexcharts-tooltip { 47 | &.apexcharts-theme-light { 48 | color: rgb(var(--v-theme-darkText)); 49 | } 50 | } 51 | .apexcharts-tooltip-series-group { 52 | &.apexcharts-active, 53 | &:last-child { 54 | padding-bottom: 0; 55 | } 56 | } 57 | .apexcharts-menu { 58 | .apexcharts-menu-item { 59 | &:hover { 60 | background: rgb(var(--v-theme-gray100)); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/layouts/full/vertical-sidebar/NavItem/NavItem.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 41 | -------------------------------------------------------------------------------- /src/views/utilities/colors/ColorPage.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 54 | -------------------------------------------------------------------------------- /src/views/dashboards/components/HelpSupport.vue: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /src/views/pages/maintenance/error/Error404Page.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 22 | 43 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | index: path.resolve(__dirname, '../dist/index.html'), 8 | assetsRoot: path.resolve(__dirname, '../dist'), 9 | assetsSubDirectory: 'assets', 10 | assetsPublicPath: '/', 11 | productionSourceMap: true, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'], 18 | // Run the build command with an extra argument to 19 | // View the bundle analyzer report after build finishes: 20 | // `npm run build --report` 21 | // Set to `true` or `false` to always turn it on or off 22 | bundleAnalyzerReport: process.env.npm_config_report 23 | }, 24 | dev: { 25 | env: require('./dev.env'), 26 | port: 8080, 27 | autoOpenBrowser: true, 28 | assetsSubDirectory: 'assets', 29 | assetsPublicPath: '/', 30 | proxyTable: {}, 31 | // CSS Sourcemaps off by default because relative paths are "buggy" 32 | // with this option, according to the CSS-Loader README 33 | // (https://github.com/webpack/css-loader#sourcemaps) 34 | // In our experience, they generally work as expected, 35 | // just be aware of this issue when enabling this option. 36 | cssSourceMap: false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/layouts/full/FullLayout.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 46 | -------------------------------------------------------------------------------- /src/views/authentication/auth/LoginPage.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 41 | 47 | -------------------------------------------------------------------------------- /src/views/authentication/auth/RegisterPage.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 41 | 47 | -------------------------------------------------------------------------------- /src/components/shared/BaseBreadcrumb.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | // ===============================|| Theme Breadcrumb ||=============================== // 17 | 46 | 47 | 54 | -------------------------------------------------------------------------------- /src/layouts/full/vertical-sidebar/NavCollapse/NavCollapse.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 43 | -------------------------------------------------------------------------------- /src/views/dashboards/components/IncomeOverview.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 79 | -------------------------------------------------------------------------------- /src/layouts/full/vertical-sidebar/VerticalSidebar.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 53 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router'; 2 | import MainRoutes from './MainRoutes'; 3 | import PublicRoutes from './PublicRoutes'; 4 | import { useAuthStore } from '@/stores/auth'; 5 | 6 | export const router = createRouter({ 7 | // history: createWebHistory(import.meta.env.BASE_URL), 8 | history: createWebHistory(), 9 | routes: [ 10 | { 11 | path: '/:pathMatch(.*)*', 12 | redirect: '/loading', 13 | // component: () => import('@/views/pages/maintenance/error/Error404Page.vue') 14 | }, 15 | MainRoutes, 16 | PublicRoutes 17 | ] 18 | }); 19 | 20 | interface User { 21 | // Define the properties and their types for the user data here 22 | // For example: 23 | id: number; 24 | name: string; 25 | } 26 | 27 | // Assuming you have a type/interface for your authentication store 28 | interface AuthStore { 29 | user: User | null; 30 | returnUrl: string | null; 31 | login(username: string, password: string): Promise; 32 | logout(): void; 33 | } 34 | 35 | router.beforeEach(async (to, from, next) => { 36 | // redirect to login page if not logged in and trying to access a restricted page 37 | const publicPages = ['/']; 38 | const auth: AuthStore = useAuthStore(); 39 | 40 | const isPublicPage = publicPages.includes(to.path); 41 | const authRequired = !isPublicPage && to.matched.some((record) => record.meta.requiresAuth); 42 | 43 | // User not logged in and trying to access a restricted page 44 | if (authRequired && !auth.user) { 45 | auth.returnUrl = to.fullPath; // Save the intended page 46 | next('/login'); 47 | } else if (auth.user && to.path === '/login') { 48 | // User logged in and trying to access the login page 49 | next({ 50 | query: { 51 | ...to.query, 52 | redirect: auth.returnUrl !== '/' ? to.fullPath : undefined 53 | } 54 | }); 55 | } else { 56 | // All other scenarios, either public page or authorized access 57 | next(); 58 | } 59 | }); 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-demo", 3 | "version": "1.3.0", 4 | "private": true, 5 | "author": "CodedThemes", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "vite", 9 | "build": "vue-tsc --noEmit && vite build", 10 | "build-stage": "vue-tsc --noEmit && vite build --base=/stage/", 11 | "build-prod": "vue-tsc --noEmit && vite build --base=/", 12 | "preview": "vite preview --port 5050", 13 | "typecheck": "vue-tsc --noEmit", 14 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-pattern .gitignore" 15 | }, 16 | "dependencies": { 17 | "@ant-design/icons-vue": "^7.0.1", 18 | "@mdi/js": "^7.4.47", 19 | "@tsconfig/node22": "^22.0.0", 20 | "@typescript-eslint/parser": "^8.19.0", 21 | "apexcharts": "4.3.0", 22 | "axios": "1.7.9", 23 | "axios-mock-adapter": "2.1.0", 24 | "chance": "1.1.12", 25 | "date-fns": "4.1.0", 26 | "lodash": "^4.17.21", 27 | "pinia": "2.3.0", 28 | "remixicon": "4.6.0", 29 | "vee-validate": "4.15.0", 30 | "vite-plugin-vuetify": "2.0.4", 31 | "vue": "3.5.13", 32 | "vue-router": "4.5.0", 33 | "vue-tabler-icons": "2.21.0", 34 | "vue3-apexcharts": "1.8.0", 35 | "vue3-perfect-scrollbar": "2.0.0", 36 | "vue3-print-nb": "0.1.4", 37 | "vuetify": "3.8.0", 38 | "webpack-plugin-vuetify": "3.0.3", 39 | "yup": "1.6.1" 40 | }, 41 | "devDependencies": { 42 | "@eslint/js": "^9.17.0", 43 | "@types/chance": "1.1.6", 44 | "@types/node": "22.10.4", 45 | "@vitejs/plugin-vue": "5.2.1", 46 | "@vue/eslint-config-prettier": "10.1.0", 47 | "@vue/tsconfig": "0.7.0", 48 | "eslint": "9.17.0", 49 | "eslint-plugin-vue": "9.32.0", 50 | "prettier": "3.4.2", 51 | "sass": "1.79.6", 52 | "sass-loader": "16.0.4", 53 | "typescript": "5.7.2", 54 | "typescript-eslint": "^8.19.0", 55 | "vite": "6.0.7", 56 | "vite-plugin-static-copy": "^2.3.0", 57 | "vue-cli-plugin-vuetify": "2.5.8", 58 | "vue-tsc": "2.2.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/utils/helpers/fetch-wrapper.ts: -------------------------------------------------------------------------------- 1 | import { useAuthStore } from '@/stores/auth'; 2 | 3 | export const fetchWrapper = { 4 | get: request('GET'), 5 | post: request('POST'), 6 | put: request('PUT'), 7 | delete: request('DELETE') 8 | }; 9 | 10 | interface temp { 11 | method: string; 12 | headers: Record; 13 | body?: string; 14 | } 15 | 16 | interface UserData { 17 | username: string; 18 | password: string; 19 | } 20 | 21 | function request(method: string) { 22 | return (url: string, body?: object) => { 23 | const requestOptions: temp = { 24 | method, 25 | headers: authHeader(url) 26 | }; 27 | if (body) { 28 | requestOptions.headers['Content-Type'] = 'application/json'; 29 | requestOptions.body = JSON.stringify(body); 30 | } 31 | return fetch(url, requestOptions).then(handleResponse); 32 | }; 33 | } 34 | 35 | // helper functions 36 | 37 | function authHeader(url: string): Record { 38 | // return auth header with jwt if user is logged in and request is to the api url 39 | const { user } = useAuthStore(); 40 | const isLoggedIn = !!user?.token; 41 | const isApiUrl = true // url.startsWith(import.meta.env.VITE_API_URL); 42 | if (isLoggedIn && isApiUrl) { 43 | return { Authorization: `Bearer ${user.token}` }; 44 | } else { 45 | return {}; 46 | } 47 | } 48 | 49 | function handleResponse(response: Response): Promise { 50 | return response.text().then((text: string) => { 51 | const data = text && JSON.parse(text); 52 | 53 | if (!response.ok) { 54 | const { user, logout } = useAuthStore(); 55 | if ([401, 403].includes(response.status) && user) { 56 | // auto logout if 401 Unauthorized or 403 Forbidden response returned from api 57 | logout(); 58 | } 59 | 60 | const error: string = (data && data.message) || response.statusText; 61 | return Promise.reject(error); 62 | } 63 | 64 | // Ensure data is of type UserData 65 | return data as any; 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /src/stores/customers.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | import { fetchWrapper } from '@/utils/helpers/fetch-wrapper'; 4 | import { ThreedCubeSphereIcon } from 'vue-tabler-icons'; 5 | import type { Customer } from '@/types'; 6 | 7 | const baseUrl = `${import.meta.env.VITE_API_URL}/customers`; 8 | 9 | 10 | export const useCustomersStore = defineStore("Customers", { 11 | 12 | state: () => ({ 13 | customers: [] as Array, 14 | loading: false, 15 | filter: '', 16 | customer: {} as Customer 17 | }), 18 | getters: { 19 | filteredData(): Customer[] { 20 | return this.customers; 21 | }, 22 | }, 23 | actions: { 24 | async getAll() { 25 | this.loading = true; 26 | this.customers = await fetchWrapper.get(baseUrl) 27 | this.loading = false; 28 | }, 29 | async delteCustomer(id: string) { 30 | const res = await fetchWrapper.delete(`${baseUrl}/${id}`) 31 | if (res.error) { 32 | console.log(res.error) 33 | } 34 | return res 35 | }, 36 | async getCustomerById(id: string) { 37 | this.loading = true 38 | this.customer = await fetchWrapper.get(`${baseUrl}/${id}`) 39 | this.loading = false 40 | }, 41 | async saveCustomer(customer: Customer) { 42 | let res 43 | if (customer.id) 44 | res = await fetchWrapper.put(`${baseUrl}/${customer.id}`, customer) 45 | else 46 | res = await fetchWrapper.post(`${baseUrl}`, customer) 47 | return res 48 | }, 49 | async newCustomer() { 50 | this.loading = true 51 | setTimeout(() => { 52 | this.customer = { 53 | id: '', 54 | firstname: '', 55 | lastname: '', 56 | fullname: '', 57 | email: '', 58 | avatar: '', 59 | mobile: '', 60 | phone: '', 61 | membership: '', 62 | rewards: 0, 63 | hasItemInShoppingCart: false 64 | } 65 | this.loading = false 66 | } 67 | , 500) 68 | 69 | } 70 | } 71 | }); 72 | -------------------------------------------------------------------------------- /src/stores/blogs.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | import { fetchWrapper } from '@/utils/helpers/fetch-wrapper'; 4 | import type { Blog } from '@/types'; 5 | import { formatToCurrencyNumber } from '@/utils/locales/format'; 6 | 7 | const baseUrl = `${import.meta.env.VITE_API_URL}/blogs`; 8 | 9 | 10 | export const useBlogsStore = defineStore("Blogs", { 11 | state: () => ({ 12 | blogs: [] as Array, 13 | loading: false, 14 | filter: '', 15 | pageSize: 10, 16 | currentPage: 1, 17 | blog: {} as Blog 18 | }), 19 | getters: { 20 | filteredData(state): Blog[] { 21 | const filtered = this.blogs.filter(p => 22 | !this.filter ? true : p.title.toLowerCase().indexOf(this.filter.toLowerCase()) > -1); 23 | const paginated = filtered.slice((state.currentPage - 1) * this.pageSize, state.currentPage *this.pageSize) 24 | return paginated 25 | 26 | }, 27 | totalPages(): number { 28 | return Math.ceil(this.blogs.length / this.pageSize); 29 | }, 30 | totalCount(): number { 31 | return this.blogs.length 32 | } 33 | }, 34 | actions: { 35 | async getAll() { 36 | this.loading = true; 37 | this.blogs = await fetchWrapper.get(baseUrl) 38 | this.loading = false; 39 | }, 40 | async delteBlog(id: string) { 41 | const res = await fetchWrapper.delete(`${baseUrl}/${id}`) 42 | if (res.error) { 43 | console.log(res.error) 44 | } 45 | return res 46 | }, 47 | async getBlogById(id: string) { 48 | this.loading = true 49 | const c = await fetchWrapper.get(`${baseUrl}/${id}`) 50 | this.blog = { ...c, 51 | price: formatToCurrencyNumber(c.price), 52 | retailPrice: formatToCurrencyNumber(c.retailPrice*1.1), 53 | } 54 | this.loading = false 55 | }, 56 | async saveBlog(blog: Blog) { 57 | let res 58 | if (blog.id) 59 | res = await fetchWrapper.put(`${baseUrl}/${blog.id}`, blog) 60 | else 61 | res = await fetchWrapper.post(`${baseUrl}`, blog) 62 | return res 63 | 64 | } 65 | } 66 | }); 67 | -------------------------------------------------------------------------------- /src/stores/orders.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | import { fetchWrapper } from '@/utils/helpers/fetch-wrapper'; 4 | import { ThreedCubeSphereIcon } from 'vue-tabler-icons'; 5 | import type { Address, Order } from '@/types'; 6 | 7 | const baseUrl = `${import.meta.env.VITE_API_URL}/orders`; 8 | 9 | 10 | export const useOrdersStore = defineStore("Orders", { 11 | 12 | state: () => ({ 13 | orders: [] as Array, 14 | loading: false, 15 | filter: '', 16 | order: {} as Order 17 | }), 18 | getters: { 19 | filteredData(): Order[] { 20 | return this.orders; 21 | }, 22 | getStepVal(): string { 23 | return (['packing','shipping','customs-clearance','delivered'] 24 | .findIndex(s=> s===this.order.delivery)+1).toString() 25 | } 26 | }, 27 | actions: { 28 | async getAll() { 29 | this.loading = true; 30 | this.orders = await fetchWrapper.get(baseUrl) 31 | this.loading = false; 32 | }, 33 | async delteOrder(id: string) { 34 | const res = await fetchWrapper.delete(`${baseUrl}/${id}`) 35 | if (res.error) { 36 | console.log(res.error) 37 | } 38 | return res 39 | }, 40 | async getOrderById(id: string) { 41 | this.loading = true 42 | const c = await fetchWrapper.get(`${baseUrl}/${id}`) 43 | this.order = c 44 | this.loading = false 45 | }, 46 | async saveOrder(order: Order) { 47 | let res 48 | if (order.id) 49 | res = await fetchWrapper.put(`${baseUrl}/${order.id}`, order) 50 | else 51 | res = await fetchWrapper.post(`${baseUrl}`, order) 52 | return res 53 | }, 54 | async newOrder() { 55 | this.loading = true 56 | setTimeout(() => { 57 | this.order = { 58 | id: '', 59 | reference: '', 60 | customer: '', 61 | lineItems: [], 62 | amount: 0, 63 | billingDate: '', 64 | shippingDate: '', 65 | shippingAddress: {} as Address, 66 | delivery: '', 67 | } 68 | this.loading = false 69 | } 70 | , 500) 71 | 72 | } 73 | } 74 | }); 75 | -------------------------------------------------------------------------------- /src/views/dashboards/components/TotalEarning.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 47 | -------------------------------------------------------------------------------- /src/views/dashboards/components/TransactionHistory.vue: -------------------------------------------------------------------------------- 1 | 36 | 59 | -------------------------------------------------------------------------------- /src/stores/auth.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { router } from '@/router'; 3 | import { fetchWrapper } from '@/utils/helpers/fetch-wrapper'; 4 | import type { AuthUser } from '@/utils/helpers/fake-backend'; 5 | 6 | const baseUrl = `${import.meta.env.VITE_API_URL}/users`; 7 | 8 | export const useAuthStore = defineStore({ 9 | id: 'auth', 10 | state: () => ({ 11 | // initialize state from local storage to enable user to stay logged in 12 | /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ 13 | // @ts-ignore 14 | user: JSON.parse(localStorage.getItem('user')), 15 | returnUrl: null, 16 | loading: false, 17 | 18 | }), 19 | actions: { 20 | async login(username: string, password: string) { 21 | const user = await fetchWrapper.post(`${baseUrl}/authenticate`, { username, password }); 22 | 23 | // update pinia state 24 | this.user = user; 25 | // store user details and jwt in local storage to keep user logged in between page refreshes 26 | localStorage.setItem('user', JSON.stringify(user)); 27 | // redirect to previous url or default to home page 28 | router.push(this.returnUrl || '/dashboard'); 29 | }, 30 | async register(username: string, password: string) { 31 | const user = await fetchWrapper.post(`${baseUrl}/authenticate`, { username, password }); 32 | 33 | // update pinia state 34 | this.user = user; 35 | // store user details and jwt in local storage to keep user logged in between page refreshes 36 | localStorage.setItem('user', JSON.stringify(user)); 37 | // redirect to previous url or default to home page 38 | router.push(this.returnUrl || '/dashboard'); 39 | }, 40 | logout() { 41 | this.user = null; 42 | localStorage.removeItem('user'); 43 | router.push('/login'); 44 | }, 45 | isAuthenticated() { 46 | this.loading = true 47 | /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ 48 | // @ts-ignore 49 | const user = JSON.parse(localStorage.getItem('user')) as AuthUser 50 | this.loading = false 51 | return user && user.id && user.username? true:false 52 | 53 | } 54 | } 55 | }); 56 | -------------------------------------------------------------------------------- /src/views/dashboards/components/WidgetFive.vue: -------------------------------------------------------------------------------- 1 | 42 | 71 | -------------------------------------------------------------------------------- /src/layouts/full/vertical-sidebar/sidebarItem.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CircleIcon, 3 | WindmillIcon, 4 | TypographyIcon, 5 | ShadowIcon, 6 | PaletteIcon, 7 | KeyIcon, 8 | BugIcon, 9 | DashboardIcon, 10 | BrandChromeIcon, 11 | Stack3Icon, 12 | UsersIcon, 13 | HelpIcon, 14 | TruckDeliveryIcon, 15 | ArticleIcon, 16 | TemplateIcon, 17 | UserDollarIcon 18 | } from 'vue-tabler-icons'; 19 | 20 | export interface menu { 21 | header?: string; 22 | title?: string; 23 | icon?: object; 24 | to?: string; 25 | divider?: boolean; 26 | chip?: string; 27 | chipColor?: string; 28 | chipVariant?: string; 29 | chipIcon?: string; 30 | children?: menu[]; 31 | disabled?: boolean; 32 | type?: string; 33 | subCaption?: string; 34 | } 35 | 36 | const sidebarItem: menu[] = [ 37 | { header: 'Home' }, 38 | { 39 | title: 'Dashboard', 40 | icon: DashboardIcon, 41 | to: '/dashboard' 42 | }, 43 | { 44 | title: 'Customer', 45 | icon: UsersIcon, 46 | to: '/customer' 47 | }, 48 | { 49 | title: 'Product', 50 | icon: Stack3Icon, 51 | to: '/product' 52 | }, 53 | { 54 | title: 'Order', 55 | icon: TruckDeliveryIcon, 56 | to: '/order' 57 | }, 58 | { divider: true }, 59 | { header: 'Marketing' } 60 | , 61 | { 62 | title: 'Blog', 63 | icon: TemplateIcon, 64 | to: '/blog' 65 | }, 66 | 67 | // { divider: true }, 68 | // { header: 'Utilities' }, 69 | // { 70 | // title: 'Typography', 71 | // icon: TypographyIcon, 72 | // to: '/utils/typography' 73 | // }, 74 | // { 75 | // title: 'Shadows', 76 | // icon: ShadowIcon, 77 | // to: '/utils/shadows' 78 | // }, 79 | // { 80 | // title: 'Colors', 81 | // icon: PaletteIcon, 82 | // to: '/utils/colors' 83 | // }, 84 | 85 | // { 86 | // title: 'Icons', 87 | // icon: WindmillIcon, 88 | // to: '/forms/radio', 89 | // children: [ 90 | // { 91 | // title: 'Tabler Icons', 92 | // icon: CircleIcon, 93 | // to: '/icons/tabler' 94 | // }, 95 | // { 96 | // title: 'Material Icons', 97 | // icon: CircleIcon, 98 | // to: '/icons/material' 99 | // } 100 | // ] 101 | // } 102 | ]; 103 | 104 | export default sidebarItem; 105 | -------------------------------------------------------------------------------- /src/views/dashboards/components/AnalyticsReport.vue: -------------------------------------------------------------------------------- 1 | 85 | 86 | 99 | -------------------------------------------------------------------------------- /src/scss/layout/_container.scss: -------------------------------------------------------------------------------- 1 | html { 2 | overflow-y: auto; 3 | } 4 | .v-main { 5 | margin-right: 20px; 6 | } 7 | @media (max-width: 1279px) { 8 | .v-main { 9 | margin: 0 10px; 10 | } 11 | } 12 | .spacer { 13 | padding: 100px 0; 14 | } 15 | @media (max-width: 800px) { 16 | .spacer { 17 | padding: 40px 0; 18 | } 19 | } 20 | 21 | .page-wrapper { 22 | // min-height: calc(100vh - 100px); 23 | min-height: calc(100vh - 150px); 24 | padding: 15px; 25 | border-radius: $border-radius-root; 26 | background: rgb(var(--v-theme-containerBg)); 27 | } 28 | $sizes: ( 29 | 'display-1': 44px, 30 | 'display-2': 40px, 31 | 'display-3': 30px, 32 | 'h1': 36px, 33 | 'h2': 30px, 34 | 'h3': 21px, 35 | 'h4': 18px, 36 | 'h5': 16px, 37 | 'h6': 14px, 38 | 'text-8': 8px, 39 | 'text-10': 10px, 40 | 'text-13': 13px, 41 | 'text-18': 18px, 42 | 'text-20': 20px, 43 | 'text-24': 24px, 44 | 'body-text-1': 10px 45 | ); 46 | 47 | @each $pixel, $size in $sizes { 48 | .#{$pixel} { 49 | font-size: $size; 50 | line-height: $size + 10; 51 | } 52 | } 53 | 54 | .customizer-btn { 55 | position: fixed; 56 | top: 25%; 57 | right: 10px; 58 | border-radius: 50% 50% 4px; 59 | .icon { 60 | animation: progress-circular-rotate 1.4s linear infinite; 61 | transform-origin: center center; 62 | transition: all 0.2s ease-in-out; 63 | } 64 | } 65 | 66 | .text-white { 67 | color: rgb(255, 255, 255) !important; 68 | } 69 | 70 | // font family 71 | 72 | body { 73 | .Poppins { 74 | font-family: 'Poppins', sans-serif !important; 75 | } 76 | 77 | .Inter { 78 | font-family: 'Inter', sans-serif !important; 79 | } 80 | } 81 | 82 | @keyframes blink { 83 | 50% { 84 | opacity: 0; 85 | } 86 | 100% { 87 | opacity: 1; 88 | } 89 | } 90 | @keyframes bounce { 91 | 0%, 92 | 20%, 93 | 53%, 94 | to { 95 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 96 | transform: translateZ(0); 97 | } 98 | 40%, 99 | 43% { 100 | animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 101 | transform: translate3d(0, -5px, 0); 102 | } 103 | 70% { 104 | animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 105 | transform: translate3d(0, -7px, 0); 106 | } 107 | 80% { 108 | transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 109 | transform: translateZ(0); 110 | } 111 | 90% { 112 | transform: translate3d(0, -2px, 0); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface Entity { 2 | id: number; 3 | text?: string; 4 | value?: number; 5 | } 6 | 7 | export interface Category extends Entity { 8 | categoryName: string; 9 | parentId: string; 10 | } 11 | 12 | export interface UserInfo extends Entity { 13 | messages: string[]; 14 | notifications: string[]; 15 | tasks: string[]; 16 | } 17 | 18 | export interface User {//extends Entity { 19 | id: string; 20 | firstname: string; 21 | lastname: string; 22 | email: string; 23 | avatar: string; 24 | mobile: string; 25 | homephone: string; 26 | workphone: string; 27 | } 28 | 29 | export interface Customer { 30 | id: string; 31 | firstname: string; 32 | lastname: string; 33 | fullname: string; 34 | email: string; 35 | avatar: string; 36 | mobile: string; 37 | phone: string; 38 | membership: string; 39 | rewards: 0; 40 | hasItemInShoppingCart: false 41 | } 42 | 43 | export interface Address { 44 | street: string; 45 | city: string; 46 | zipcode: string; 47 | country: string; 48 | } 49 | 50 | export interface Order { 51 | id: string; 52 | reference: string; 53 | customer: string; 54 | lineItems: Product[]; 55 | amount: number; 56 | billingDate: string; 57 | shippingDate: string; 58 | shippingAddress: Address; 59 | delivery: string; 60 | } 61 | 62 | export interface Product { 63 | id: string; 64 | name: string; 65 | category: string; 66 | unitInStock: string; 67 | price: number; 68 | retailPrice: number; 69 | colors?: []; 70 | imageUri?: string; 71 | releaseDate?: string; 72 | status?: string 73 | } 74 | 75 | export interface State { 76 | items: Entity[]; 77 | pagination: Pagination; 78 | loading: boolean; 79 | mode: string; 80 | snackbar: boolean; 81 | notice: string; 82 | customer: Customer; 83 | orders: Order[]; 84 | orderList: Order[]; 85 | } 86 | 87 | 88 | export type SearchFilter = { 89 | equal?: TODO, 90 | contain?: TODO, 91 | startsWith?: TODO, 92 | endsWith?: TODO, 93 | lessThan?: TODO, 94 | greaterThan?: TODO, 95 | lessThanOrEqual?: TODO, 96 | greaterThanOrEqual?: TODO, 97 | filters?: TODO 98 | 99 | } 100 | 101 | export interface Blog { 102 | id: string; 103 | title: string; 104 | description: string; 105 | coverUrl: string; 106 | totalViews: string; 107 | totalComments:string; 108 | totalShares: string; 109 | totalFavorites: string; 110 | postedAt: string; 111 | author: { 112 | name: string; 113 | avatar: string; 114 | }, 115 | } -------------------------------------------------------------------------------- /src/stores/products.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | import { fetchWrapper } from '@/utils/helpers/fetch-wrapper'; 4 | import type { Product } from '@/types'; 5 | import { formatToCurrencyNumber } from '@/utils/locales/format'; 6 | 7 | const baseUrl = `${import.meta.env.VITE_API_URL}/products`; 8 | 9 | 10 | 11 | export const useProductsStore = defineStore("Products", { 12 | state: () => ({ 13 | products: [] as Array, 14 | loading: false, 15 | filter: '', 16 | pageSize: 10, 17 | currentPage: 1, 18 | product: {} as Product 19 | }), 20 | getters: { 21 | filteredData(state): Product[] { 22 | const filtered = this.products.filter(p => 23 | !this.filter ? true : p.name.toLowerCase().indexOf(this.filter.toLowerCase()) > -1); 24 | const paginated = filtered.slice((state.currentPage - 1) * this.pageSize, state.currentPage * this.pageSize) 25 | return paginated 26 | 27 | }, 28 | totalPages(): number { 29 | return Math.ceil(this.products.length / this.pageSize); 30 | }, 31 | totalCount(): number { 32 | return this.products.length 33 | } 34 | }, 35 | actions: { 36 | async getAll() { 37 | this.loading = true; 38 | const list = await fetchWrapper.get(baseUrl) 39 | this.products = list 40 | this.loading = false; 41 | }, 42 | async delteProduct(id: string) { 43 | const res = await fetchWrapper.delete(`${baseUrl}/${id}`) 44 | if (res.error) { 45 | console.log(res.error) 46 | } 47 | return res 48 | }, 49 | async getProductById(id: string) { 50 | this.loading = true 51 | const c = await fetchWrapper.get(`${baseUrl}/${id}`) 52 | this.product = { 53 | ...c, 54 | price: formatToCurrencyNumber(c.price), 55 | retailPrice: formatToCurrencyNumber(c.price * 1.1), 56 | } 57 | this.loading = false 58 | }, 59 | async saveProduct(product: Product) { 60 | let res 61 | if (product.id) 62 | res = await fetchWrapper.put(`${baseUrl}/${product.id}`, product) 63 | else 64 | res = await fetchWrapper.post(`${baseUrl}`, product) 65 | return res 66 | }, 67 | async newProduct() { 68 | this.loading = true 69 | setTimeout(() => { 70 | this.product = { 71 | id: '', 72 | name: '', 73 | category: '', 74 | unitInStock: '', 75 | price: 0, 76 | retailPrice: 0, 77 | // category: Category, 78 | imageUri: '', 79 | releaseDate: '' 80 | } 81 | this.loading = false 82 | } 83 | , 500) 84 | 85 | } 86 | } 87 | }); 88 | -------------------------------------------------------------------------------- /src/assets/images/maintenance/img-error-text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/views/dashboards/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 73 | -------------------------------------------------------------------------------- /src/layouts/full/vertical-header/ProfileDD.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 79 | -------------------------------------------------------------------------------- /src/scss/layout/_sidebar.scss: -------------------------------------------------------------------------------- 1 | /*This is for the logo*/ 2 | .leftSidebar { 3 | border: 0px; 4 | box-shadow: none !important; 5 | .logo { 6 | padding-left: 7px; 7 | } 8 | } 9 | /*This is for the Vertical sidebar*/ 10 | .scrollnavbar { 11 | height: calc(100vh - 100px); 12 | .smallCap { 13 | padding: 0px 0 0 4px !important; 14 | font-size: 0.875rem; 15 | font-weight: 500; 16 | } 17 | .v-list { 18 | color: rgb(var(--v-theme-lightText)); 19 | } 20 | /*General Menu css*/ 21 | .v-list-group__items .v-list-item, 22 | .v-list-item { 23 | border-radius: $border-radius-root; 24 | padding-inline-start: calc(12px + var(--indent-padding) / 2) !important; 25 | &:hover { 26 | color: rgb(var(--v-theme-secondary)); 27 | } 28 | .v-list-item__prepend { 29 | margin-inline-end: 13px; 30 | } 31 | .v-list-item__append { 32 | font-size: 0.875rem; 33 | .v-icon { 34 | margin-inline-start: 13px; 35 | } 36 | } 37 | .v-list-item-title { 38 | font-size: 0.875rem; 39 | } 40 | } 41 | .v-list-group__items .v-list-item { 42 | margin-inline-start: calc(2px + var(--indent-padding) / 2); 43 | padding-inline-start: 16px !important; 44 | } 45 | .leftPadding { 46 | margin-left: 4px; 47 | } 48 | /*This is for the dropdown*/ 49 | .v-list { 50 | .v-list-item--active { 51 | .v-list-item-title { 52 | font-weight: 500; 53 | } 54 | } 55 | .sidebarchip .v-icon { 56 | margin-inline-start: -3px; 57 | } 58 | .v-list-group { 59 | .v-list-item:hover > .v-list-item__overlay, 60 | .v-list-item--active > .v-list-item__overlay { 61 | background-color: transparent; 62 | } 63 | .v-list-item:focus-visible > .v-list-item__overlay { 64 | opacity: 0; 65 | } 66 | } 67 | > .v-list-group { 68 | position: relative; 69 | > .v-list-item--active, 70 | > .v-list-item:hover { 71 | background: rgb(var(--v-theme-secondary), 0.05); 72 | } 73 | 74 | &:after { 75 | content: ''; 76 | position: absolute; 77 | left: 21px; 78 | top: 46px; 79 | height: calc(100% - 46px); 80 | width: 1px; 81 | opacity: 1; 82 | background: rgb(var(--v-theme-primary), 0.15); 83 | } 84 | } 85 | } 86 | } 87 | .v-navigation-drawer--rail { 88 | .scrollnavbar .v-list .v-list-group__items, 89 | .hide-menu { 90 | opacity: 0; 91 | } 92 | .leftPadding { 93 | margin-left: 0px; 94 | } 95 | } 96 | @media only screen and (min-width: 1170px) { 97 | .mini-sidebar { 98 | .logo { 99 | width: 40px; 100 | overflow: hidden; 101 | } 102 | .leftSidebar:hover { 103 | box-shadow: $box-shadow !important; 104 | } 105 | .v-navigation-drawer--expand-on-hover:hover { 106 | .logo { 107 | width: 100%; 108 | } 109 | .v-list .v-list-group__items, 110 | .hide-menu { 111 | opacity: 1; 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/router/MainRoutes.ts: -------------------------------------------------------------------------------- 1 | const MainRoutes = { 2 | path: '/main', 3 | meta: { 4 | requiresAuth: true 5 | }, 6 | redirect: '/main/dashboard', 7 | component: () => import('@/layouts/full/FullLayout.vue'), 8 | children: [ 9 | { 10 | name: 'LandingPage', 11 | path: '/', 12 | component: () => import('@/views/dashboards/Dashboard.vue') 13 | }, 14 | { 15 | name: 'Dashboard', 16 | path: '/dashboard', 17 | component: () => import('@/views/dashboards/Dashboard.vue') 18 | }, 19 | { 20 | name: 'Customer', 21 | path: '/customer', 22 | component: () => import('@/views/pages/CustomerView.vue') 23 | }, 24 | { 25 | name: 'Edit Customer', 26 | path: '/customer/:id', 27 | component: () => import('@/views/pages/CustomerForm.vue') 28 | }, 29 | 30 | { 31 | name: 'New Customer', 32 | path: '/customer/new', 33 | component: () => import('@/views/pages/CustomerForm.vue') 34 | }, 35 | { 36 | name: 'Product', 37 | path: '/product', 38 | component: () => import('@/views/pages/ProductView.vue') 39 | }, 40 | { 41 | name: 'Edit Product', 42 | path: '/product/:id', 43 | component: () => import('@/views/pages/ProductForm.vue') 44 | }, 45 | 46 | { 47 | name: 'New Product', 48 | path: '/product/new', 49 | component: () => import('@/views/pages/ProductForm.vue') 50 | }, 51 | { 52 | name: 'Order', 53 | path: '/order', 54 | component: () => import('@/views/pages/OrderView.vue') 55 | }, 56 | { 57 | name: 'Edit Order', 58 | path: '/order/:id', 59 | component: () => import('@/views/pages/OrderForm.vue') 60 | }, 61 | 62 | { 63 | name: 'New Order', 64 | path: '/order/new', 65 | component: () => import('@/views/pages/OrderForm.vue') 66 | }, 67 | { 68 | name: 'Blog', 69 | path: '/blog', 70 | component: () => import('@/views/pages/BlogView.vue') 71 | }, 72 | { 73 | name: 'Error 404', 74 | path: '/error', 75 | component: () => import('@/views/pages/maintenance/error/Error404Page.vue') 76 | } 77 | // , 78 | // { 79 | // name: 'Tabler Icons', 80 | // path: '/icons/tabler', 81 | // component: () => import('@/views/utilities/icons/TablerIcons.vue') 82 | // }, 83 | // { 84 | // name: 'Material Icons', 85 | // path: '/icons/material', 86 | // component: () => import('@/views/utilities/icons/MaterialIcons.vue') 87 | // }, 88 | // { 89 | // name: 'Typography', 90 | // path: '/utils/typography', 91 | // component: () => import('@/views/utilities/typography/TypographyPage.vue') 92 | // }, 93 | // { 94 | // name: 'Shadows', 95 | // path: '/utils/shadows', 96 | // component: () => import('@/views/utilities/shadows/ShadowPage.vue') 97 | // }, 98 | // { 99 | // name: 'Colors', 100 | // path: '/utils/colors', 101 | // component: () => import('@/views/utilities/colors/ColorPage.vue') 102 | // }, 103 | ] 104 | }; 105 | 106 | export default MainRoutes; 107 | -------------------------------------------------------------------------------- /src/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:math'; 2 | @use 'sass:map'; 3 | @use 'sass:meta'; 4 | @use 'vuetify/lib/styles/tools/functions' as *; 5 | 6 | // This will false all colors which is not necessory for theme 7 | $color-pack: false; 8 | 9 | // Global font size and border radius 10 | $font-size-root: 1rem; 11 | $border-radius-root: 12px; 12 | $body-font-family: 'Roboto', sans-serif !default; 13 | $heading-font-family: $body-font-family !default; 14 | $btn-font-weight: 400 !default; 15 | $btn-letter-spacing: 0 !default; 16 | 17 | // Global Radius as per breakeven point 18 | $rounded: () !default; 19 | $rounded: map-deep-merge( 20 | ( 21 | 0: 0, 22 | 'sm': $border-radius-root * 0.5, 23 | null: $border-radius-root, 24 | 'md': $border-radius-root * 1, 25 | 'lg': $border-radius-root * 2, 26 | 'xl': $border-radius-root * 6, 27 | 'pill': 9999px, 28 | 'circle': 50%, 29 | 'shaped': $border-radius-root * 6 0 30 | ), 31 | $rounded 32 | ); 33 | // Global Typography 34 | $typography: () !default; 35 | $typography: map-deep-merge( 36 | ( 37 | 'h1': ( 38 | 'size': 2.125rem, 39 | 'weight': 700, 40 | 'line-height': 3.5rem, 41 | 'font-family': inherit 42 | ), 43 | 'h2': ( 44 | 'size': 1.5rem, 45 | 'weight': 700, 46 | 'line-height': 2.5rem, 47 | 'font-family': inherit 48 | ), 49 | 'h3': ( 50 | 'size': 1.25rem, 51 | 'weight': 600, 52 | 'line-height': 2rem, 53 | 'font-family': inherit 54 | ), 55 | 'h4': ( 56 | 'size': 1rem, 57 | 'weight': 600, 58 | 'line-height': 1.5rem, 59 | 'font-family': inherit 60 | ), 61 | 'h5': ( 62 | 'size': 0.875rem, 63 | 'weight': 500, 64 | 'line-height': 1.2rem, 65 | 'font-family': inherit 66 | ), 67 | 'h6': ( 68 | 'size': 0.75rem, 69 | 'weight': 500, 70 | 'font-family': inherit 71 | ), 72 | 'subtitle-1': ( 73 | 'size': 0.875rem, 74 | 'weight': 500, 75 | 'line-height': 1rem, 76 | 'font-family': inherit 77 | ), 78 | 'subtitle-2': ( 79 | 'size': 0.75rem, 80 | 'weight': 400, 81 | 'line-height': 1rem, 82 | 'font-family': inherit 83 | ), 84 | 'body-1': ( 85 | 'size': 0.875rem, 86 | 'weight': 400, 87 | 'font-family': inherit 88 | ), 89 | 'body-2': ( 90 | 'size': 0.75rem, 91 | 'weight': 400, 92 | 'font-family': inherit 93 | ), 94 | 'button': ( 95 | 'size': 0.875rem, 96 | 'weight': 500, 97 | 'font-family': inherit, 98 | 'text-transform': uppercase 99 | ), 100 | 'caption': ( 101 | 'size': 0.75rem, 102 | 'weight': 400, 103 | 'font-family': inherit 104 | ), 105 | 'overline': ( 106 | 'size': 0.75rem, 107 | 'weight': 500, 108 | 'font-family': inherit, 109 | 'text-transform': uppercase 110 | ) 111 | ), 112 | $typography 113 | ); 114 | 115 | // Custom Variables 116 | // colors 117 | $white: #fff !default; 118 | 119 | // cards 120 | $card-item-spacer-xy: 20px 24px !default; 121 | $card-text-spacer: 24px !default; 122 | $card-title-size: 18px !default; 123 | // Global Shadow 124 | $box-shadow: 1px 0 20px rgb(0 0 0 / 8%); 125 | -------------------------------------------------------------------------------- /src/assets/images/maintenance/img-error-purple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/views/dashboards/components/TotalGrowth.vue: -------------------------------------------------------------------------------- 1 | 99 | 100 | 133 | -------------------------------------------------------------------------------- /src/views/utilities/typography/TypographyPage.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 11# Vue Demo App V3 2 | 3 | > A reusable Vue.js starter project for real-world business based on Vue 3 with Vuetify 3 and Pinia. 4 | 5 | The goal of this project is to build a reusable starter project for real-world business. To achieve this target, we need a solution which includes state management (Pinia), fake restful API and elegant UI design (Vuetify). 6 | 7 | 8 | ### Live Demo 9 | 10 | [Latest Demo App](https://vue-app-demo.harryho.org?timestamp=v3): The demo is just a proof of concept. It doesn't have back-end API and all features of master branch. 11 | 12 | [Previous version Demo App](https://vue-demo-v2.harryho.org?timestamp=v2): The demo is just a proof of concept. It doesn't have back-end API and all features of master branch. Source code is availabe on a different [branch](https://github.com/harryho/vue-crm/tree/v2-vtf2) 13 | 14 | 15 | ### Screenshots 16 | 17 | #### Latest Version 18 | ![Screenshot1](screenshots/v3/Screenshot-6.png) 19 | 20 | 21 | ![Screenshot1](screenshots/v3/Screenshot-3.png) 22 | 23 | ![Screenshot1](screenshots/v3/Screenshot-5.png) 24 | 25 | 26 | 27 | #### Previous Version 28 | 29 | 30 | ![Screenshot2](screenshots/screenshot-2.png) 31 | 32 | ![Screenshot3](screenshots/screenshot-3.png) 33 | 34 | 37 | 38 | ## Build Setup 39 | 40 | ``` bash 41 | 42 | # Clone project 43 | git clone https://github.com/harryho/vue-crm.git 44 | 45 | 46 | # install dependences for Vue 2 CRM 47 | cd vue-crm 48 | npm install 49 | 50 | 51 | # serve with hot reload at localhost:8080 52 | npm run dev 53 | 54 | 55 | ``` 56 | 57 | ## Docker 58 | 59 | 60 | ```bash 61 | ## Run / Test release without building new image 62 | npm run build 63 | 64 | # Launch nginx image to test latest release 65 | docker pull nginx:alpine 66 | docker run -p 8080:80 -v \ 67 | /dist:/usr/share/nginx/html nginx:alpine 68 | 69 | 70 | # Build release image 71 | docker build . -t vue-demo:3.0 72 | 73 | # Launch the development image in the backgroud 74 | docker run --rm -d --publish 8080:80 --name vd3 vue-demo:3.0 75 | 76 | # Check the log 77 | docker logs vd3 -f 78 | 79 | ``` 80 | 81 | 82 | For detailed explanation on how things work, checkout following links 83 | 84 | * [vue](https://vuex.vuejs.org/en/) 85 | * [vuetifyjs](https://dev.vuetifyjs.com/) 86 | * [Pinia](https://pinia.vuejs.org/) 87 | 88 | 89 | #### Change log 90 | 91 | * Mar 2025 - Uplift to Vue 3 + Vuetify 3 + Pinia is done. 92 | 93 | * Dec 2024 - Uplift to Vue 3 + Vuetify 3 94 | 95 | * 2 May 2020 - Merge the branch vuetify-ts to master 96 | 97 | After the merge, the whole project moved to new techncial stack - TypeScript. Also, the VuetifyJs is upgraded to 2.x version. 98 | 99 | 100 | * 6 Dec 2018 - Create an archived branch json-server 101 | 102 | This branch was the master which used Json-Server as fake API. Considering the hiccup of setting Json-Server up and maintenance, it will be replaced by fake service ( Readonly fake API). You still can find and clone this branch with the name __json-server__, but it is no longer updated. It is an archived branch. 103 | 104 | 105 | * 27 May 2018 - Rebase demo branch to master 106 | 107 | New master doesn't rely on Json-Server as fake API. It will only have Readonly fake API. It means any new or updated data will be stored to any physical file. All test data will be rolled back after system restart. 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/scss/pages/_dashboards.scss: -------------------------------------------------------------------------------- 1 | .bubble-shape { 2 | position: relative; 3 | &:before { 4 | content: ''; 5 | position: absolute; 6 | width: 210px; 7 | height: 210px; 8 | border-radius: 50%; 9 | top: -125px; 10 | right: -15px; 11 | opacity: 0.5; 12 | } 13 | &:after { 14 | content: ''; 15 | position: absolute; 16 | width: 210px; 17 | height: 210px; 18 | border-radius: 50%; 19 | top: -85px; 20 | right: -95px; 21 | } 22 | 23 | &.bubble-primary-shape { 24 | &::before { 25 | background: rgb(var(--v-theme-darkprimary)); 26 | } 27 | &::after { 28 | background: rgb(var(--v-theme-darkprimary)); 29 | } 30 | } 31 | 32 | &.bubble-secondary-shape { 33 | &::before { 34 | background: rgb(var(--v-theme-darksecondary)); 35 | } 36 | &::after { 37 | background: rgb(var(--v-theme-darksecondary)); 38 | } 39 | } 40 | } 41 | 42 | .z-1 { 43 | z-index: 1; 44 | position: relative; 45 | } 46 | .bubble-shape-sm { 47 | position: relative; 48 | &::before { 49 | content: ''; 50 | position: absolute; 51 | width: 210px; 52 | height: 210px; 53 | border-radius: 50%; 54 | top: -160px; 55 | right: -130px; 56 | } 57 | &.bubble-primary { 58 | &::before { 59 | background: linear-gradient(140.9deg, rgb(var(--v-theme-lightprimary)) -14.02%, rgba(var(--v-theme-darkprimary), 0) 77.58%); 60 | } 61 | } 62 | &::after { 63 | content: ''; 64 | position: absolute; 65 | width: 210px; 66 | height: 210px; 67 | border-radius: 50%; 68 | top: -30px; 69 | right: -180px; 70 | } 71 | &.bubble-primary { 72 | &::after { 73 | background: linear-gradient(210.04deg, rgb(var(--v-theme-lightprimary)) -50.94%, rgba(var(--v-theme-darkprimary), 0) 83.49%); 74 | } 75 | } 76 | 77 | &.bubble-warning { 78 | &::before { 79 | background: linear-gradient(140.9deg, rgb(var(--v-theme-warning)) -14.02%, rgba(144, 202, 249, 0) 70.5%); 80 | } 81 | } 82 | 83 | &.bubble-warning { 84 | &::after { 85 | background: linear-gradient(210.04deg, rgb(var(--v-theme-warning)) -50.94%, rgba(144, 202, 249, 0) 83.49%); 86 | } 87 | } 88 | } 89 | 90 | .rounded-square { 91 | width: 20px; 92 | height: 20px; 93 | } 94 | 95 | .icon-scale { 96 | position: absolute; 97 | left: -17px; 98 | bottom: -27px; 99 | transform: rotate(25deg); 100 | } 101 | 102 | .tabBtn { 103 | .v-btn--variant-outlined:not(.v-tab--selected) { 104 | border: none; 105 | color: rgb(var(--v-theme-lightText)); 106 | } 107 | } 108 | 109 | .headerWithBtn { 110 | .v-card-item { 111 | padding: 14px; 112 | } 113 | } 114 | 115 | .widget-gradient { 116 | position: relative; 117 | &::before, 118 | &::after { 119 | content: ''; 120 | width: 100%; 121 | height: 100%; 122 | position: absolute; 123 | background: linear-gradient(90deg, rgba(255, 255, 255, 0.0001) 22.07%, rgba(255, 255, 255, 0.15) 83.21%); 124 | transform: matrix(0.9, 0.44, -0.44, 0.9, 0, 0); 125 | } 126 | &:after { 127 | top: 50%; 128 | right: -20px; 129 | } 130 | &::before { 131 | right: -70px; 132 | bottom: 80%; 133 | } 134 | } 135 | .hover-icon-card { 136 | .hover-icon { 137 | transition: 0.5s; 138 | } 139 | &:hover { 140 | .hover-icon { 141 | opacity: 1; 142 | transform: scale(1.2); 143 | transition: 0.5s; 144 | } 145 | } 146 | } 147 | .widget-progress { 148 | .v-progress-linear { 149 | .v-progress-linear__background { 150 | background-color: rgb(var(--v-theme-lightsuccess)) !important; 151 | opacity: 1; 152 | } 153 | } 154 | } 155 | .readMedia { 156 | position: absolute; 157 | bottom: -7px; 158 | right: 0; 159 | width: 182px; 160 | height: 144px; 161 | } 162 | -------------------------------------------------------------------------------- /src/layouts/full/vertical-header/VerticalHeader.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 102 | -------------------------------------------------------------------------------- /src/views/dashboards/components/RecentOrder.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 119 | -------------------------------------------------------------------------------- /src/assets/images/maintenance/img-error-bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/views/authentication/authForms/AuthLogin.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 92 | 124 | -------------------------------------------------------------------------------- /src/assets/images/logos/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/images/logos/logolight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/views/authentication/authForms/AuthRegister.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 106 | 133 | -------------------------------------------------------------------------------- /src/views/dashboards/components/UniqueVisitor.vue: -------------------------------------------------------------------------------- 1 | 131 | 132 | 158 | 159 | -------------------------------------------------------------------------------- /src/views/dashboards/components/TotalOrder.vue: -------------------------------------------------------------------------------- 1 | 122 | 123 | 174 | -------------------------------------------------------------------------------- /src/views/pages/CustomerView.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 120 | -------------------------------------------------------------------------------- /src/views/dashboards/components/SalesReport.vue: -------------------------------------------------------------------------------- 1 | 162 | 163 | 190 | 201 | -------------------------------------------------------------------------------- /src/layouts/full/customizer/CustomizerPanel.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 80 | 139 | --------------------------------------------------------------------------------