├── .editorconfig ├── .env.example ├── .eslintrc.js ├── .github ├── COMMIT_CONVENTION.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── assets │ ├── sponsors.png │ └── vuestic-admin-logo.png └── dependabot.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .storybook ├── main.ts ├── preview.ts └── storybook-main.scss ├── .vscode └── extensions.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.ja-JP.md ├── README.md ├── README.zh-CN.md ├── _redirects ├── docs └── pre-production.md ├── index.html ├── netlify.toml ├── package.json ├── postcss.config.js ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── site.webmanifest ├── vuestic-admin-image.png └── vuestic-admin-logo.png ├── src ├── App.vue ├── components │ ├── NotFoundImage.vue │ ├── VuesticLogo.stories.ts │ ├── VuesticLogo.vue │ ├── app-layout-navigation │ │ └── AppLayoutNavigation.vue │ ├── icons │ │ ├── VaIconCleanCode.vue │ │ ├── VaIconColor.vue │ │ ├── VaIconDiscord.vue │ │ ├── VaIconFaster.vue │ │ ├── VaIconFree.vue │ │ ├── VaIconFresh.vue │ │ ├── VaIconGitHub.vue │ │ ├── VaIconHideSidebar.vue │ │ ├── VaIconMenu.vue │ │ ├── VaIconMenuCollapsed.vue │ │ ├── VaIconMessage.vue │ │ ├── VaIconNotification.vue │ │ ├── VaIconResponsive.vue │ │ ├── VaIconRich.vue │ │ ├── VaIconSlower.vue │ │ ├── VaIconVue.vue │ │ └── VaIconVuestic.vue │ ├── navbar │ │ ├── AppNavbar.vue │ │ └── components │ │ │ ├── AppNavbarActions.vue │ │ │ ├── GitHubButton.vue │ │ │ └── dropdowns │ │ │ ├── NotificationDropdown.vue │ │ │ └── ProfileDropdown.vue │ ├── sidebar │ │ ├── AppSidebar.vue │ │ └── NavigationRoutes.ts │ ├── typography │ │ ├── Typography.stories.ts │ │ └── Typography.vue │ ├── va-charts │ │ ├── VaChart.vue │ │ ├── chart-types │ │ │ ├── BarChart.vue │ │ │ ├── BubbleChart.vue │ │ │ ├── DoughnutChart.vue │ │ │ ├── HorizontalBarChart.vue │ │ │ ├── LineChart.vue │ │ │ ├── Map.vue │ │ │ └── PieChart.vue │ │ ├── external-tooltip.ts │ │ └── vaChartConfigs.js │ ├── va-medium-editor │ │ ├── VaMediumEditor.vue │ │ └── _variables.scss │ └── va-timeline-item.vue ├── data │ ├── CountriesList.ts │ ├── charts │ │ ├── barChartData.ts │ │ ├── bubbleChartData.ts │ │ ├── composables │ │ │ ├── useChartColors.ts │ │ │ └── useChartData.ts │ │ ├── doughnutChartData.ts │ │ ├── horizontalBarChartData.ts │ │ ├── index.ts │ │ ├── lineChartData.ts │ │ ├── pieChartData.ts │ │ └── revenueChartData.ts │ ├── geo.json │ ├── pages │ │ ├── projects-db.json │ │ ├── projects.ts │ │ ├── users-db.json │ │ └── users.ts │ ├── types.ts │ └── users.json ├── env.d.ts ├── i18n │ ├── index.ts │ └── locales │ │ ├── br.json │ │ ├── cn.json │ │ ├── es.json │ │ ├── gb.json │ │ └── ir.json ├── layouts │ ├── AppLayout.vue │ ├── AuthLayout.vue │ └── RouterBypass.vue ├── main.ts ├── pages │ ├── 404.vue │ ├── admin │ │ ├── dashboard │ │ │ ├── Dashboard.vue │ │ │ ├── DataSection.vue │ │ │ ├── DataSectionItem.vue │ │ │ └── cards │ │ │ │ ├── MonthlyEarnings.vue │ │ │ │ ├── ProjectTable.vue │ │ │ │ ├── RegionRevenue.vue │ │ │ │ ├── RevenueByLocationMap.vue │ │ │ │ ├── RevenueReport.vue │ │ │ │ ├── RevenueReportChart.vue │ │ │ │ ├── Timeline.vue │ │ │ │ └── YearlyBreakup.vue │ │ └── pages │ │ │ └── 404PagesPage.vue │ ├── auth │ │ ├── CheckTheEmail.vue │ │ ├── Login.vue │ │ ├── RecoverPassword.vue │ │ └── Signup.vue │ ├── billing │ │ ├── BillingPage.vue │ │ ├── Invoices.vue │ │ ├── MembeshipTier.vue │ │ ├── PaymentInfo.vue │ │ ├── modals │ │ │ └── ChangeYourPaymentPlan.vue │ │ └── types.ts │ ├── faq │ │ ├── FaqPage.vue │ │ ├── data │ │ │ ├── navigationLinks.json │ │ │ └── popularCategories.json │ │ ├── request-demo.svg │ │ └── widgets │ │ │ ├── Categories.vue │ │ │ ├── Navigation.vue │ │ │ ├── Questions.vue │ │ │ └── RequestDemo.vue │ ├── payments │ │ ├── PaymentsPage.vue │ │ ├── payment-system │ │ │ ├── PaymentSystem.stories.ts │ │ │ ├── PaymentSystem.vue │ │ │ ├── mastercard.png │ │ │ └── visa.png │ │ ├── types.ts │ │ └── widgets │ │ │ ├── billing-address │ │ │ ├── BillingAddressCreateModal.stories.ts │ │ │ ├── BillingAddressCreateModal.vue │ │ │ ├── BillingAddressEdit.stories.ts │ │ │ ├── BillingAddressEdit.vue │ │ │ ├── BillingAddressList.stories.ts │ │ │ ├── BillingAddressList.vue │ │ │ ├── BillingAddressListItem.stories.ts │ │ │ ├── BillingAddressListItem.vue │ │ │ ├── BillingAddressUpdateModal.stories.ts │ │ │ └── BillingAddressUpdateModal.vue │ │ │ └── my-cards │ │ │ ├── PaymentCardCreateModal.stories.ts │ │ │ ├── PaymentCardCreateModal.vue │ │ │ ├── PaymentCardEdit.stories.ts │ │ │ ├── PaymentCardEdit.vue │ │ │ ├── PaymentCardList.stories.ts │ │ │ ├── PaymentCardList.vue │ │ │ ├── PaymentCardListItem.stories.ts │ │ │ ├── PaymentCardListItem.vue │ │ │ ├── PaymentCardUpdateModal.stories.ts │ │ │ └── PaymentCardUpdateModal.vue │ ├── preferences │ │ ├── Preferences.vue │ │ ├── modals │ │ │ ├── EditNameModal.vue │ │ │ └── ResetPasswordModal.vue │ │ ├── preferences-header │ │ │ └── PreferencesHeader.vue │ │ ├── settings │ │ │ └── Settings.vue │ │ └── styles.ts │ ├── pricing-plans │ │ ├── PricingPlans.vue │ │ ├── options.ts │ │ └── styles.ts │ ├── projects │ │ ├── ProjectsPage.vue │ │ ├── components │ │ │ └── ProjectStatusBadge.vue │ │ ├── composables │ │ │ ├── useProjectStatusColor.ts │ │ │ ├── useProjectUsers.ts │ │ │ └── useProjects.ts │ │ ├── types.ts │ │ └── widgets │ │ │ ├── EditProjectForm.vue │ │ │ ├── ProjectCards.vue │ │ │ └── ProjectsTable.vue │ ├── settings │ │ ├── Settings.vue │ │ ├── language-switcher │ │ │ └── LanguageSwitcher.vue │ │ ├── notifications │ │ │ └── Notifications.vue │ │ └── theme-switcher │ │ │ └── ThemeSwitcher.vue │ └── users │ │ ├── UsersPage.vue │ │ ├── composables │ │ └── useUsers.ts │ │ ├── types.ts │ │ └── widgets │ │ ├── EditUserForm.vue │ │ ├── UserAvatar.vue │ │ └── UsersTable.vue ├── router │ └── index.ts ├── scss │ ├── icon-fonts │ │ ├── index.scss │ │ └── vuestic-icons │ │ │ ├── vuestic-icons.eot │ │ │ ├── vuestic-icons.scss │ │ │ ├── vuestic-icons.svg │ │ │ ├── vuestic-icons.ttf │ │ │ └── vuestic-icons.woff │ ├── main.scss │ ├── tailwind.scss │ └── vuestic.scss ├── services │ ├── api.ts │ ├── toCSV.ts │ ├── utils.ts │ └── vuestic-ui │ │ ├── global-config.ts │ │ ├── icons-config │ │ ├── aliases.ts │ │ └── icons-config.ts │ │ └── themes.ts └── stores │ ├── billing-addresses.ts │ ├── global-store.ts │ ├── index.ts │ ├── notifications.ts │ ├── payment-cards.ts │ ├── projects.ts │ ├── user-store.ts │ └── users.ts ├── tailwind.config.js ├── tsconfig.json ├── vite.config.ts └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | VITE_APP_GTM_KEY= 2 | 3 | VITE_APP_INCLUDE_DEMOS= 4 | 5 | VITE_APP_ROUTER_MODE_HISTORY= 6 | 7 | VITE_APP_BUILD_VERSION= 8 | 9 | VITE_API_BASE_URL= 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | es2021: true, 6 | node: true, 7 | 'vue/setup-compiler-macros': true, 8 | }, 9 | plugins: ['@typescript-eslint'], 10 | parser: 'vue-eslint-parser', 11 | parserOptions: { 12 | parser: '@typescript-eslint/parser', 13 | sourceType: 'module', 14 | ecmaVersion: 2021, 15 | }, 16 | extends: [ 17 | 'eslint:recommended', 18 | 'plugin:@typescript-eslint/recommended', 19 | 'plugin:vue/vue3-recommended', 20 | '@vue/typescript/recommended', 21 | '@vue/prettier', 22 | 'plugin:storybook/recommended', 23 | ], 24 | 25 | rules: { 26 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 28 | 'prettier/prettier': ['warn', {}, { usePrettierrc: true, indent: 2 }], 29 | '@typescript-eslint/no-non-null-assertion': 0, 30 | '@typescript-eslint/no-explicit-any': 0, // allow explicit any's because of the legacy code and ts-less deps, but still prohibit IMplicit any's 31 | 'vue/multi-word-component-names': 0, 32 | 'vue/no-lone-template': 0, 33 | 'vue/v-on-event-hyphenation': ['warn', 'never', { autofix: true }], 34 | 'vue/component-name-in-template-casing': ['warn', 'PascalCase', { registeredComponentsOnly: false }], 35 | 'vue/script-indent': ['warn', 2], // , { baseIndent: 0 } - we should use that, but it didn't work for me for some reason. 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Vue.js Contributing Guide 2 | 3 | Hi! We are really excited that you are interested in contributing to Vuestic. Before submitting your contribution though, please make sure to take a moment and read through the following guidelines. 4 | 5 | - [Code of Conduct](./../CODE_OF_CONDUCT.md) 6 | 7 | ## Pull Request Guidelines 8 | 9 | - The `master` branch is just a snapshot of the latest stable release. **Do not submit PRs against the `master` branch.** 10 | - Atomic code contribution looks something like this: 11 | - Checkout from upstream `develop`. 12 | - Work on your fork in dedicated branch. 13 | - When you're ready to show results - create PR against upstream `develop` and add a developer for review. You can ping said developer to speed things up ;). 14 | - It's OK to have multiple small commits as you work on the PR - we will let GitHub automatically squash it before merging. 15 | 16 | - Good stuff to add in your pull request: 17 | - If your PR fully resolves existing issue, add `(fix #xxxx[,#xxx])` (#xxxx is the issue id) so that github will close the issue once it's up on `master`. You have to add that to the body of PR, won't work in header :). 18 | - Provide detailed description of the issue in the PR if it's not done in the issue. 19 | - If you're working on visual changes - provide before/after screenshot. That speeds up review immensely. 20 | 21 | ### Branches 22 | 23 | - Upstream branches (**epicmax/vuestic-admin**): 24 | 25 | - `master` - stable snapshot from `develop`. Releases and hotfixes only. Do not submit PR's to `master`!. 26 | - `develop` - main development branch. 27 | 28 | - Local branches 29 | - For local branches naming stick to [commit message convention](./COMMIT_CONVENTION.md). So for feature branch that adds tabs name would be `feat/tabs`. 30 | 31 | ### For core contributors 32 | 33 | - Keep amount of local branches minimal. 34 | - Always link PR to issue (via `fix #123`). 35 | - For small issues you may push to `develop` branch directly while adding (`fix #123`) to commit message. 36 | - Create single PR for one issue. If we have several PRs - move all the code into a single one and close the rest. If one PR covers several issues - either split it in several PRs or mark one of the issues as duplicate. 37 | - Be sure to have only one person assigned per issue. 38 | - Check your code: https://github.com/epicmaxco/vuestic-admin/issues/378. 39 | - We use [yarn](https://yarnpkg.com/lang/en/) for package management. 40 | - Be proactive. If you think something is wrong - create an issue or discuss. 41 | - Recommended tools: [GitKraken](https://www.gitkraken.com/), [WebStorm](https://www.jetbrains.com/webstorm/), [ShareX](https://getsharex.com/) 42 | 43 | #### Before release workflow 44 | 45 | - Update package versions to newest ones. Update lock files (for both `npm` and `yarn`) 46 | 47 | ### Vuestic-ui 48 | 49 | Vuestic-admin uses vuestic-ui internally. So if you have some troubles with components - it's better to submit issue or PR in [respective repo](https://github.com/epicmaxco/vuestic-ui). 50 | 51 | ### Commonly used NPM scripts 52 | 53 | ```bash 54 | # run dev server 55 | $ yarn dev 56 | 57 | # build vuestic-admin project into bundle 58 | $ yarn build 59 | ``` 60 | 61 | ## Credits 62 | 63 | Hall of fame! 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Note: for support questions, please use stackoverflow**. This repository's issues are reserved for feature requests and bug reports. 2 | 3 | - **I'm submitting a ...** 4 | 5 | - [ ] bug report 6 | - [ ] feature request 7 | - [ ] support request => Please do not submit support request here, see note at the top of this template. 8 | 9 | - **Do you want to request a _feature_ or report a _bug_?** 10 | 11 | - **What is the current behavior?** 12 | 13 | - **If the current behavior is a bug, please provide the steps to reproduce, ideally also a screenshot or gif if it's a style issue** 14 | 15 | - **What is the expected behavior?** 16 | 17 | - **What is the motivation / use case for changing the behavior?** 18 | 19 | - **Other information** (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. stackoverflow, gitter, etc) 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | ## Description 10 | 11 | 12 | 13 | ## Markup: 14 | 15 | 16 |
17 | 18 | ```vue 19 | // Your code 20 | ``` 21 | 22 |
23 | 24 | ## Types of changes 25 | 26 | 27 | 28 | - [ ] Bug fix (non-breaking change which fixes an issue) 29 | - [ ] New feature (non-breaking change which adds functionality) 30 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 31 | - [ ] Improvement/refactoring (non-breaking change that doesn't add any feature but make things better) 32 | -------------------------------------------------------------------------------- /.github/assets/sponsors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicmaxco/vuestic-admin/fa573e20e8a4bf8faeccf2982a4e1a572767168d/.github/assets/sponsors.png -------------------------------------------------------------------------------- /.github/assets/vuestic-admin-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicmaxco/vuestic-admin/fa573e20e8a4bf8faeccf2982a4e1a572767168d/.github/assets/vuestic-admin-logo.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | target-branch: "develop" 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | .env 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | 27 | # Local Netlify folder 28 | .netlify 29 | 30 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore everything recursively... 2 | * 3 | 4 | # But not the .{ts,js,html,css,scss,vue,json,md} files 5 | !*.ts 6 | !*.js 7 | !*.html 8 | !*.css 9 | !*.scss 10 | !*.vue 11 | !*.json 12 | !*.md 13 | 14 | # But still ignore the dist folder 15 | dist/** 16 | 17 | # Check subdirectories too 18 | !*/ 19 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": false, 4 | "singleQuote": true, 5 | "quoteProps": "as-needed", 6 | "trailingComma": "all", 7 | "bracketSpacing": true, 8 | "printWidth": 120 9 | } 10 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from '@storybook/vue3-vite' 2 | 3 | const config: StorybookConfig = { 4 | stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], 5 | addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'], 6 | framework: { 7 | name: '@storybook/vue3-vite', 8 | options: {}, 9 | }, 10 | docs: { 11 | autodocs: 'tag', 12 | }, 13 | } 14 | export default config 15 | -------------------------------------------------------------------------------- /.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import { type Preview, setup } from '@storybook/vue3' 2 | import { createVuestic } from 'vuestic-ui' 3 | import vuesticGlobalConfig from '../src/services/vuestic-ui/global-config' 4 | import './storybook-main.scss' 5 | import '../src/scss/main.scss' 6 | 7 | import { createPinia } from 'pinia' 8 | 9 | const pinia = createPinia() 10 | 11 | setup((app) => { 12 | app.use(createVuestic({ config: vuesticGlobalConfig })) 13 | app.use(pinia) 14 | }) 15 | 16 | const preview: Preview = { 17 | parameters: { 18 | actions: { argTypesRegex: '^on[A-Z].*' }, 19 | controls: { 20 | matchers: { 21 | color: /(background|color)$/i, 22 | date: /Date$/, 23 | }, 24 | }, 25 | }, 26 | } 27 | 28 | export default preview 29 | -------------------------------------------------------------------------------- /.storybook/storybook-main.scss: -------------------------------------------------------------------------------- 1 | @import url('https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.min.css'); 2 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'); 3 | @import url(https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined); 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Epicmax LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /docs/pre-production.md: -------------------------------------------------------------------------------- 1 | # Pre-production 2 | 3 | ## SEO 4 | 5 | We have a boierplate prepared with some analytics ingrained. This includes: 6 | 7 | - [Yandex Metrica](https://metrica.yandex.com/about) 8 | 9 | To get these running - just provide keys to respective apis. You're advised to copy `.env.example` with rename to `.env` then modify it. 10 | 11 | Notice, that if you are about to use Google Maps then you also have to provide it with your personal API key. The key must be defined under the `VUE_APP_GOOGLE_MAPS_API_KEY` environment-variable (more on them below) and can be obtained [here](https://developers.google.com/maps/documentation/javascript/get-api-key). 12 | 13 | ## Deploy 14 | 15 | We use [circleci](https://circleci.com) to deploy vuestic version you're able to see on demo. 16 | 17 | If you want to save some time and use our config, do notice that circleci will need the following keys, that you have to set in **Build Settings -> Environment Variables**. 18 | 19 | - `DEPLOY_PASSWORD` ssh password. 20 | - `DEPLOY_PATH_PRODUCTION` production build will be loaded to this folder. 21 | - `DEPLOY_PATH_STAGING` staging build will be loaded to this folder. 22 | - `DEPLOY_URL` ssh url. 23 | - `DEPLOY_USER` ssh password. 24 | 25 | You can modify [config](../.circleci/config.yml) if our solution doesn't suit your needs exactly. 26 | 27 | Couple of things to note: 28 | 29 | - in `.env` file keys should look like this `VUE_APP_DRIFT_KEY`. Which will correspond to circleci key `DRIFT_KEY`. You essentially have two ways to pass config into build process. 30 | - Circleci will run tests before both staging and production. 31 | 32 | ## Demos 33 | 34 | You can enable demos in build by: 35 | 36 | ``` 37 | VUE_APP_INCLUDE_DEMOS=true 38 | ``` 39 | 40 | Demos are included in staging build by default. They're not present in production because of significant impact on bundle size. 41 | 42 | ## Build Version 43 | 44 | You can enable build version, hash commit, and timestamp by build to the main page footer: 45 | 46 | ``` 47 | VUE_APP_BUILD_VERSION=true 48 | ``` 49 | 50 | This information are excluded by default. 51 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | Vuestic Admin 15 | 16 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "dist" 3 | 4 | # Default build command. 5 | command = "yarn build:ci" 6 | 7 | [[redirects]] 8 | from = "/*" 9 | to = "/index.html" 10 | status = 200 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuestic-admin", 3 | "private": true, 4 | "version": "3.1.0", 5 | "scripts": { 6 | "prepare": "husky install", 7 | "dev": "vite", 8 | "build": "npm run lint && vue-tsc --noEmit && vite build", 9 | "build:ci": "vite build", 10 | "start:ci": "serve -s ./dist", 11 | "prelint": "npm run format", 12 | "lint": "eslint \"./src/**/*.{ts,js,vue}\" --fix", 13 | "format": "prettier --write .", 14 | "preview": "vite preview", 15 | "storybook": "storybook dev -p 6006", 16 | "build-storybook": "storybook build" 17 | }, 18 | "lint-staged": { 19 | "./src/**/*.{ts,js,vue}": [ 20 | "npm run lint" 21 | ] 22 | }, 23 | "dependencies": { 24 | "@gtm-support/vue-gtm": "^2.0.0", 25 | "@vuestic/compiler": "latest", 26 | "@vuestic/tailwind": "^0.1.3", 27 | "@vueuse/core": "^10.6.1", 28 | "chart.js": "^4.4.1", 29 | "chartjs-chart-geo": "^4.2.8", 30 | "epic-spinners": "^2.0.0", 31 | "flag-icons": "^6.15.0", 32 | "ionicons": "^4.6.3", 33 | "medium-editor": "^5.23.3", 34 | "pinia": "^2.1.7", 35 | "register-service-worker": "^1.7.1", 36 | "sass": "^1.69.5", 37 | "serve": "^14.2.1", 38 | "uuid": "^11.0.3", 39 | "vue": "3.5.8", 40 | "vue-chartjs": "^5.3.0", 41 | "vue-i18n": "^9.6.2", 42 | "vue-router": "^4.2.5", 43 | "vuestic-ui": "^1.10.2" 44 | }, 45 | "devDependencies": { 46 | "@intlify/unplugin-vue-i18n": "^1.5.0", 47 | "@storybook/addon-essentials": "^7.4.6", 48 | "@storybook/addon-interactions": "^7.4.6", 49 | "@storybook/addon-links": "^7.4.6", 50 | "@storybook/blocks": "^7.4.6", 51 | "@storybook/testing-library": "^0.2.2", 52 | "@storybook/vue3": "^7.4.6", 53 | "@storybook/vue3-vite": "^7.4.6", 54 | "@types/medium-editor": "^5.0.5", 55 | "@types/node": "^20.9.0", 56 | "@typescript-eslint/eslint-plugin": "^6.11.0", 57 | "@typescript-eslint/parser": "^6.11.0", 58 | "@vitejs/plugin-vue": "^5.2.1", 59 | "@vue/eslint-config-prettier": "^8.0.0", 60 | "@vue/eslint-config-typescript": "^12.0.0", 61 | "autoprefixer": "^10.4.13", 62 | "eslint": "^8.13.0", 63 | "eslint-plugin-prettier": "^5.0.1", 64 | "eslint-plugin-storybook": "^0.6.15", 65 | "eslint-plugin-vue": "^9.18.1", 66 | "husky": "^8.0.1", 67 | "lint-staged": "^15.1.0", 68 | "postcss": "^8.4.21", 69 | "prettier": "^3.1.0", 70 | "storybook": "^7.4.6", 71 | "tailwindcss": "^3.4.0", 72 | "typescript": "^5.2.2", 73 | "vite": "^5.4.9", 74 | "vue-eslint-parser": "^9.3.2", 75 | "vue-tsc": "^2.1.6" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicmaxco/vuestic-admin/fa573e20e8a4bf8faeccf2982a4e1a572767168d/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicmaxco/vuestic-admin/fa573e20e8a4bf8faeccf2982a4e1a572767168d/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicmaxco/vuestic-admin/fa573e20e8a4bf8faeccf2982a4e1a572767168d/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicmaxco/vuestic-admin/fa573e20e8a4bf8faeccf2982a4e1a572767168d/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicmaxco/vuestic-admin/fa573e20e8a4bf8faeccf2982a4e1a572767168d/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicmaxco/vuestic-admin/fa573e20e8a4bf8faeccf2982a4e1a572767168d/public/favicon.ico -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /public/vuestic-admin-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicmaxco/vuestic-admin/fa573e20e8a4bf8faeccf2982a4e1a572767168d/public/vuestic-admin-image.png -------------------------------------------------------------------------------- /public/vuestic-admin-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicmaxco/vuestic-admin/fa573e20e8a4bf8faeccf2982a4e1a572767168d/public/vuestic-admin-logo.png -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | -------------------------------------------------------------------------------- /src/components/VuesticLogo.stories.ts: -------------------------------------------------------------------------------- 1 | import VuesticLogo from './VuesticLogo.vue' 2 | 3 | export default { 4 | title: 'VuesticLogo', 5 | component: VuesticLogo, 6 | tags: ['autodocs'], 7 | } 8 | 9 | export const Default = () => ({ 10 | components: { VuesticLogo }, 11 | template: ``, 12 | }) 13 | 14 | export const White = () => ({ 15 | components: { VuesticLogo }, 16 | template: `
17 | 18 |
`, 19 | }) 20 | 21 | export const Blue = () => ({ 22 | components: { VuesticLogo }, 23 | template: ``, 24 | }) 25 | 26 | export const Height = () => ({ 27 | components: { VuesticLogo }, 28 | template: ``, 29 | }) 30 | -------------------------------------------------------------------------------- /src/components/app-layout-navigation/AppLayoutNavigation.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 91 | 92 | 97 | -------------------------------------------------------------------------------- /src/components/icons/VaIconCleanCode.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 41 | -------------------------------------------------------------------------------- /src/components/icons/VaIconColor.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 31 | 32 | 39 | -------------------------------------------------------------------------------- /src/components/icons/VaIconDiscord.vue: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /src/components/icons/VaIconFaster.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 28 | -------------------------------------------------------------------------------- /src/components/icons/VaIconFree.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 47 | -------------------------------------------------------------------------------- /src/components/icons/VaIconFresh.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 35 | -------------------------------------------------------------------------------- /src/components/icons/VaIconGitHub.vue: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /src/components/icons/VaIconHideSidebar.vue: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /src/components/icons/VaIconMenu.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | 24 | 31 | -------------------------------------------------------------------------------- /src/components/icons/VaIconMenuCollapsed.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 25 | 26 | 33 | -------------------------------------------------------------------------------- /src/components/icons/VaIconMessage.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | 21 | 28 | -------------------------------------------------------------------------------- /src/components/icons/VaIconNotification.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | -------------------------------------------------------------------------------- /src/components/icons/VaIconResponsive.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 30 | -------------------------------------------------------------------------------- /src/components/icons/VaIconRich.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 41 | -------------------------------------------------------------------------------- /src/components/icons/VaIconSlower.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 28 | -------------------------------------------------------------------------------- /src/components/icons/VaIconVue.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 35 | -------------------------------------------------------------------------------- /src/components/navbar/AppNavbar.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 39 | 40 | 80 | -------------------------------------------------------------------------------- /src/components/navbar/components/AppNavbarActions.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 54 | 55 | 98 | -------------------------------------------------------------------------------- /src/components/navbar/components/GitHubButton.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/components/sidebar/NavigationRoutes.ts: -------------------------------------------------------------------------------- 1 | export interface INavigationRoute { 2 | name: string 3 | displayName: string 4 | meta: { icon: string } 5 | children?: INavigationRoute[] 6 | } 7 | 8 | export default { 9 | root: { 10 | name: '/', 11 | displayName: 'navigationRoutes.home', 12 | }, 13 | routes: [ 14 | { 15 | name: 'dashboard', 16 | displayName: 'menu.dashboard', 17 | meta: { 18 | icon: 'vuestic-iconset-dashboard', 19 | }, 20 | }, 21 | { 22 | name: 'users', 23 | displayName: 'menu.users', 24 | meta: { 25 | icon: 'group', 26 | }, 27 | }, 28 | { 29 | name: 'projects', 30 | displayName: 'menu.projects', 31 | meta: { 32 | icon: 'folder_shared', 33 | }, 34 | }, 35 | { 36 | name: 'payments', 37 | displayName: 'menu.payments', 38 | meta: { 39 | icon: 'credit_card', 40 | }, 41 | children: [ 42 | { 43 | name: 'payment-methods', 44 | displayName: 'menu.payment-methods', 45 | }, 46 | { 47 | name: 'pricing-plans', 48 | displayName: 'menu.pricing-plans', 49 | }, 50 | { 51 | name: 'billing', 52 | displayName: 'menu.billing', 53 | }, 54 | ], 55 | }, 56 | { 57 | name: 'auth', 58 | displayName: 'menu.auth', 59 | meta: { 60 | icon: 'login', 61 | }, 62 | children: [ 63 | { 64 | name: 'login', 65 | displayName: 'menu.login', 66 | }, 67 | { 68 | name: 'signup', 69 | displayName: 'menu.signup', 70 | }, 71 | { 72 | name: 'recover-password', 73 | displayName: 'menu.recover-password', 74 | }, 75 | ], 76 | }, 77 | 78 | { 79 | name: 'faq', 80 | displayName: 'menu.faq', 81 | meta: { 82 | icon: 'quiz', 83 | }, 84 | }, 85 | { 86 | name: '404', 87 | displayName: 'menu.404', 88 | meta: { 89 | icon: 'vuestic-iconset-files', 90 | }, 91 | }, 92 | { 93 | name: 'preferences', 94 | displayName: 'menu.preferences', 95 | meta: { 96 | icon: 'manage_accounts', 97 | }, 98 | }, 99 | { 100 | name: 'settings', 101 | displayName: 'menu.settings', 102 | meta: { 103 | icon: 'settings', 104 | }, 105 | }, 106 | ] as INavigationRoute[], 107 | } 108 | -------------------------------------------------------------------------------- /src/components/typography/Typography.stories.ts: -------------------------------------------------------------------------------- 1 | import Typography from './Typography.vue' 2 | 3 | export default { 4 | title: 'Typography', 5 | component: Typography, 6 | tags: ['autodocs'], 7 | } 8 | 9 | export const Default = () => ({ 10 | components: { Typography }, 11 | template: ` 12 | 13 | `, 14 | }) 15 | -------------------------------------------------------------------------------- /src/components/va-charts/VaChart.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 27 | 28 | 48 | -------------------------------------------------------------------------------- /src/components/va-charts/chart-types/BarChart.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | -------------------------------------------------------------------------------- /src/components/va-charts/chart-types/BubbleChart.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | -------------------------------------------------------------------------------- /src/components/va-charts/chart-types/DoughnutChart.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | -------------------------------------------------------------------------------- /src/components/va-charts/chart-types/HorizontalBarChart.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 27 | -------------------------------------------------------------------------------- /src/components/va-charts/chart-types/LineChart.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 73 | -------------------------------------------------------------------------------- /src/components/va-charts/chart-types/Map.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 67 | -------------------------------------------------------------------------------- /src/components/va-charts/chart-types/PieChart.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | -------------------------------------------------------------------------------- /src/components/va-charts/vaChartConfigs.js: -------------------------------------------------------------------------------- 1 | import { defineAsyncComponent, markRaw } from 'vue' 2 | 3 | const DEFAULT_FONT_FAMILY = "'Inter', sans-serif" 4 | 5 | export const defaultConfig = { 6 | scales: { 7 | x: { 8 | ticks: { 9 | font: { 10 | family: DEFAULT_FONT_FAMILY, 11 | }, 12 | }, 13 | }, 14 | y: { 15 | ticks: { 16 | font: { 17 | family: DEFAULT_FONT_FAMILY, 18 | }, 19 | }, 20 | }, 21 | }, 22 | plugins: { 23 | legend: { 24 | position: 'bottom', 25 | labels: { 26 | font: { 27 | color: '#34495e', 28 | family: DEFAULT_FONT_FAMILY, 29 | size: 14, 30 | }, 31 | usePointStyle: true, 32 | }, 33 | }, 34 | tooltip: { 35 | bodyFont: { 36 | size: 14, 37 | family: DEFAULT_FONT_FAMILY, 38 | }, 39 | boxPadding: 4, 40 | }, 41 | }, 42 | datasets: { 43 | line: { 44 | fill: 'origin', 45 | tension: 0.3, 46 | borderColor: 'transparent', 47 | }, 48 | bubble: { 49 | borderColor: 'transparent', 50 | }, 51 | bar: { 52 | borderColor: 'transparent', 53 | }, 54 | }, 55 | maintainAspectRatio: false, 56 | animation: true, 57 | } 58 | 59 | export const doughnutConfig = { 60 | cutout: '80%', 61 | scales: { 62 | x: { 63 | display: false, 64 | grid: { 65 | display: false, // Disable X-axis grid lines ("net") 66 | }, 67 | }, 68 | y: { 69 | display: false, 70 | grid: { 71 | display: false, // Disable Y-axis grid lines ("net") 72 | }, 73 | ticks: { 74 | display: false, // Hide Y-axis values 75 | }, 76 | }, 77 | }, 78 | plugins: { 79 | legend: { 80 | display: false, 81 | }, 82 | }, 83 | datasets: { 84 | line: { 85 | fill: 'origin', 86 | tension: 0.3, 87 | borderColor: 'transparent', 88 | }, 89 | bubble: { 90 | borderColor: 'transparent', 91 | }, 92 | bar: { 93 | borderColor: 'transparent', 94 | }, 95 | }, 96 | maintainAspectRatio: false, 97 | animation: true, 98 | } 99 | 100 | export const chartTypesMap = { 101 | pie: markRaw(defineAsyncComponent(() => import('./chart-types/PieChart.vue'))), 102 | doughnut: markRaw(defineAsyncComponent(() => import('./chart-types/DoughnutChart.vue'))), 103 | bubble: markRaw(defineAsyncComponent(() => import('./chart-types/BubbleChart.vue'))), 104 | line: markRaw(defineAsyncComponent(() => import('./chart-types/LineChart.vue'))), 105 | bar: markRaw(defineAsyncComponent(() => import('./chart-types/BarChart.vue'))), 106 | 'horizontal-bar': markRaw(defineAsyncComponent(() => import('./chart-types/HorizontalBarChart.vue'))), 107 | } 108 | -------------------------------------------------------------------------------- /src/components/va-medium-editor/_variables.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --va-medium-editor-margin-bottom: 2.25rem; 3 | --va-medium-editor-min-width: 6rem; 4 | --va-medium-editor-max-width: 600px; 5 | 6 | /* Toolbar */ 7 | --va-medium-editor-toolbar-max-width: 90%; 8 | --va-medium-editor-toolbar-box-shadow: none; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/va-timeline-item.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 29 | 30 | 83 | -------------------------------------------------------------------------------- /src/data/charts/barChartData.ts: -------------------------------------------------------------------------------- 1 | import { TBarChartData } from '../types' 2 | 3 | export const barChartData: TBarChartData = { 4 | labels: [ 5 | 'January', 6 | 'February', 7 | 'March', 8 | 'April', 9 | 'May', 10 | 'June', 11 | 'July', 12 | 'August', 13 | 'September', 14 | 'October', 15 | 'November', 16 | 'December', 17 | ], 18 | datasets: [ 19 | { 20 | label: 'Last year', 21 | backgroundColor: 'primary', 22 | data: [50, 20, 12, 39, 10, 40, 39, 80, 40, 20, 12, 11], 23 | }, 24 | { 25 | label: 'Current year', 26 | backgroundColor: 'info', 27 | data: [50, 10, 22, 39, 15, 20, 85, 32, 60, 50, 20, 30], 28 | }, 29 | ], 30 | } 31 | -------------------------------------------------------------------------------- /src/data/charts/composables/useChartColors.ts: -------------------------------------------------------------------------------- 1 | import { computed, ref, watch } from 'vue' 2 | import { useColors, useGlobalConfig } from 'vuestic-ui' 3 | 4 | type chartColors = string | string[] 5 | 6 | export function useChartColors(chartColors = [] as chartColors, alfa = 0.6) { 7 | const { getGlobalConfig } = useGlobalConfig() 8 | const { setHSLAColor, getColor } = useColors() 9 | 10 | const generateHSLAColors = (colors: chartColors) => 11 | typeof colors === 'string' 12 | ? setHSLAColor(getColor(colors), { a: alfa }) 13 | : colors.map((color) => setHSLAColor(getColor(color), { a: alfa })) 14 | 15 | const generateColors = (colors: chartColors) => 16 | typeof colors === 'string' ? getColor(colors) : colors.map((color) => getColor(color)) 17 | 18 | const generatedHSLAColors = ref(generateHSLAColors(chartColors)) 19 | const generatedColors = ref(generateColors(chartColors)) 20 | 21 | const theme = computed(() => getGlobalConfig().colors!) 22 | 23 | watch(theme, () => { 24 | generatedHSLAColors.value = generateHSLAColors(chartColors) 25 | generatedColors.value = generateColors(chartColors) 26 | }) 27 | 28 | return { 29 | generateHSLAColors, 30 | generateColors, 31 | generatedColors, 32 | generatedHSLAColors, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/data/charts/composables/useChartData.ts: -------------------------------------------------------------------------------- 1 | import { computed, ComputedRef } from 'vue' 2 | import { useChartColors } from './useChartColors' 3 | import { TChartData } from '../../types' 4 | 5 | export function useChartData(data: T, alfa?: number): ComputedRef { 6 | const datasetsColors = data.datasets.map((dataset) => dataset.backgroundColor as string) 7 | 8 | const datasetsThemesColors = datasetsColors.map( 9 | (colors) => useChartColors(colors, alfa)[alfa ? 'generatedHSLAColors' : 'generatedColors'], 10 | ) 11 | 12 | return computed(() => { 13 | const datasets = data.datasets.map((dataset, idx) => ({ 14 | ...dataset, 15 | backgroundColor: datasetsThemesColors[idx].value, 16 | })) 17 | 18 | return { ...data, datasets } as T 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /src/data/charts/doughnutChartData.ts: -------------------------------------------------------------------------------- 1 | import { TDoughnutChartData } from '../types' 2 | 3 | export const profitBackground = '#154EC1' 4 | export const expensesBackground = '#fff' 5 | export const earningsBackground = '#ECF0F1' 6 | 7 | export const doughnutChartData: TDoughnutChartData = { 8 | labels: ['Profit', 'Expenses'], 9 | datasets: [ 10 | { 11 | label: 'Yearly Breakdown', 12 | backgroundColor: [profitBackground, earningsBackground], 13 | data: [432, 167], 14 | }, 15 | ], 16 | } 17 | -------------------------------------------------------------------------------- /src/data/charts/horizontalBarChartData.ts: -------------------------------------------------------------------------------- 1 | import { TBarChartData } from '../types' 2 | 3 | export const horizontalBarChartData: TBarChartData = { 4 | labels: [ 5 | 'January', 6 | 'February', 7 | 'March', 8 | 'April', 9 | 'May', 10 | 'June', 11 | 'July', 12 | 'August', 13 | 'September', 14 | 'October', 15 | 'November', 16 | 'December', 17 | ], 18 | datasets: [ 19 | { 20 | label: 'Vuestic Satisfaction Score', 21 | backgroundColor: 'primary', 22 | data: [80, 90, 50, 70, 60, 90, 50, 90, 80, 40, 72, 93], 23 | }, 24 | { 25 | label: 'Bulma Satisfaction Score', 26 | backgroundColor: 'danger', 27 | data: [20, 30, 20, 40, 50, 40, 15, 60, 30, 20, 42, 53], 28 | }, 29 | ], 30 | } 31 | -------------------------------------------------------------------------------- /src/data/charts/index.ts: -------------------------------------------------------------------------------- 1 | export { bubbleChartData } from './bubbleChartData' 2 | export { doughnutChartData } from './doughnutChartData' 3 | export { barChartData } from './barChartData' 4 | export { horizontalBarChartData } from './horizontalBarChartData' 5 | export { lineChartData } from './lineChartData' 6 | export { pieChartData } from './pieChartData' 7 | 8 | // TODO: clean up charts data, after dashboard rework 9 | -------------------------------------------------------------------------------- /src/data/charts/lineChartData.ts: -------------------------------------------------------------------------------- 1 | import { TLineChartData } from '../types' 2 | 3 | export const lineChartData: TLineChartData = { 4 | labels: [ 5 | 'January', 6 | 'February', 7 | 'March', 8 | 'April', 9 | 'May', 10 | 'June', 11 | 'July', 12 | 'August', 13 | 'September', 14 | 'October', 15 | 'November', 16 | 'December', 17 | ], 18 | datasets: [ 19 | { 20 | label: 'Monthly Earnings', 21 | backgroundColor: 'rgba(75,192,192,0.4)', 22 | data: [10, 35, 14, 17, 12, 40, 75, 55, 30, 51, 25, 7], // Random values 23 | }, 24 | ], 25 | } 26 | -------------------------------------------------------------------------------- /src/data/charts/pieChartData.ts: -------------------------------------------------------------------------------- 1 | import { TLineChartData } from '../types' 2 | 3 | export const pieChartData: TLineChartData = { 4 | labels: ['Africa', 'Asia', 'Europe'], 5 | datasets: [ 6 | { 7 | label: 'Population (millions)', 8 | backgroundColor: ['primary', 'warning', 'danger'], 9 | data: [2478, 5267, 734], 10 | }, 11 | ], 12 | } 13 | -------------------------------------------------------------------------------- /src/data/charts/revenueChartData.ts: -------------------------------------------------------------------------------- 1 | export const earningsColor = '#49A8FF' 2 | export const expensesColor = '#154EC1' 3 | 4 | export const months: string[] = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 5 | 6 | export type Revenues = { 7 | month: string 8 | earning: number 9 | expenses: number 10 | } 11 | 12 | export const generateRevenues = (months: string[]): Revenues[] => { 13 | return months.map((month: string) => { 14 | const earning = Math.floor(Math.random() * 100000 + 10000) 15 | return { 16 | month, 17 | earning, 18 | expenses: Math.floor(earning * Math.random()), 19 | } 20 | }) 21 | } 22 | 23 | export const getRevenuePerMonth = (month: string, revenues: Revenues[]): Revenues => { 24 | const revenue = revenues.find((revenue) => revenue.month === month) 25 | return revenue || { month, earning: 0, expenses: 0 } 26 | } 27 | 28 | export const formatMoney = (amount: number, currency = 'USD'): string => { 29 | return new Intl.NumberFormat('en-US', { 30 | style: 'currency', 31 | currency, 32 | }).format(amount) 33 | } 34 | -------------------------------------------------------------------------------- /src/data/pages/projects.ts: -------------------------------------------------------------------------------- 1 | import api from '../../services/api' 2 | import { Project } from '../../pages/projects/types' 3 | 4 | export type Pagination = { 5 | page: number 6 | perPage: number 7 | total: number 8 | } 9 | 10 | export type Sorting = { 11 | sortBy: 'project_owner' | 'team' | 'created_at' 12 | sortingOrder: 'asc' | 'desc' | null 13 | } 14 | 15 | export const getProjects = async (options: Partial & Pagination) => { 16 | const projects: Project[] = await fetch(api.allProjects()).then((r) => r.json()) 17 | 18 | return { 19 | data: projects, 20 | pagination: { 21 | page: options.page, 22 | perPage: options.perPage, 23 | total: projects.length, 24 | }, 25 | } 26 | } 27 | 28 | export const addProject = async (project: Omit) => { 29 | const headers = new Headers() 30 | headers.append('Content-Type', 'application/json') 31 | 32 | return fetch(api.allProjects(), { method: 'POST', body: JSON.stringify(project), headers }).then((r) => r.json()) 33 | } 34 | 35 | export const updateProject = async (project: Omit) => { 36 | const headers = new Headers() 37 | headers.append('Content-Type', 'application/json') 38 | return fetch(api.project(project.id), { method: 'PUT', body: JSON.stringify(project), headers }).then((r) => r.json()) 39 | } 40 | 41 | export const removeProject = async (project: Project) => { 42 | return fetch(api.project(project.id), { method: 'DELETE' }) 43 | } 44 | -------------------------------------------------------------------------------- /src/data/pages/users.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../../pages/users/types' 2 | import api from '../../services/api' 3 | 4 | export type Pagination = { 5 | page: number 6 | perPage: number 7 | total: number 8 | } 9 | 10 | export type Sorting = { 11 | sortBy: keyof User | undefined 12 | sortingOrder: 'asc' | 'desc' | null 13 | } 14 | 15 | export type Filters = { 16 | isActive: boolean 17 | search: string 18 | } 19 | 20 | export const getUsers = async (filters: Partial) => { 21 | const { isActive, search } = filters 22 | let filteredUsers: User[] = await fetch(api.allUsers()).then((r) => r.json()) 23 | 24 | filteredUsers = filteredUsers.filter((user) => user.active === isActive) 25 | 26 | if (search) { 27 | filteredUsers = filteredUsers.filter((user) => user.fullname.toLowerCase().includes(search.toLowerCase())) 28 | } 29 | 30 | const { page = 1, perPage = 10 } = filters || {} 31 | return { 32 | data: filteredUsers, 33 | pagination: { 34 | page, 35 | perPage, 36 | total: filteredUsers.length, 37 | }, 38 | } 39 | } 40 | 41 | export const addUser = async (user: User) => { 42 | const headers = new Headers() 43 | headers.append('Content-Type', 'application/json') 44 | 45 | const result = await fetch(api.allUsers(), { method: 'POST', body: JSON.stringify(user), headers }).then((r) => 46 | r.json(), 47 | ) 48 | 49 | if (!result.error) { 50 | return result 51 | } 52 | 53 | throw new Error(result.error) 54 | } 55 | 56 | export const updateUser = async (user: User) => { 57 | const headers = new Headers() 58 | headers.append('Content-Type', 'application/json') 59 | 60 | const result = await fetch(api.user(user.id), { method: 'PUT', body: JSON.stringify(user), headers }).then((r) => 61 | r.json(), 62 | ) 63 | 64 | if (!result.error) { 65 | return result 66 | } 67 | 68 | throw new Error(result.error) 69 | } 70 | 71 | export const removeUser = async (user: User) => { 72 | return fetch(api.user(user.id), { method: 'DELETE' }) 73 | } 74 | 75 | export const uploadAvatar = async (body: FormData) => { 76 | return fetch(api.avatars(), { method: 'POST', body, redirect: 'follow' }).then((r) => r.json()) 77 | } 78 | -------------------------------------------------------------------------------- /src/data/types.ts: -------------------------------------------------------------------------------- 1 | import type { ChartData } from 'chart.js' 2 | 3 | export type ColorThemes = { 4 | [key: string]: string 5 | } 6 | 7 | export type TLineChartData = ChartData<'line', any, any> 8 | export type TBarChartData = ChartData<'bar', any, any> 9 | export type TBubbleChartData = ChartData<'bubble', any, any> 10 | export type TDoughnutChartData = ChartData<'doughnut', any, any> 11 | export type TPieChartData = ChartData<'pie', any, any> 12 | 13 | export type TChartData = TLineChartData | TBarChartData | TBubbleChartData | TDoughnutChartData | TPieChartData 14 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import { createI18n } from 'vue-i18n' 2 | 3 | const fileNameToLocaleModuleDict = import.meta.glob<{ default: Record }>('./locales/*.json', { 4 | eager: true, 5 | }) 6 | 7 | const messages: { [P: string]: Record } = {} 8 | Object.entries(fileNameToLocaleModuleDict) 9 | .map(([fileName, localeModule]) => { 10 | const fileNameParts = fileName.split('/') 11 | const fileNameWithoutPath = fileNameParts[fileNameParts.length - 1] 12 | const localeName = fileNameWithoutPath.split('.json')[0] 13 | 14 | return [localeName, localeModule.default] as const 15 | }) 16 | .forEach((localeNameLocaleMessagesTuple) => { 17 | messages[localeNameLocaleMessagesTuple[0]] = localeNameLocaleMessagesTuple[1] 18 | }) 19 | 20 | export default createI18n({ 21 | legacy: false, 22 | locale: 'gb', 23 | fallbackLocale: 'gb', 24 | messages, 25 | }) 26 | -------------------------------------------------------------------------------- /src/layouts/AppLayout.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 84 | 85 | 92 | -------------------------------------------------------------------------------- /src/layouts/AuthLayout.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 42 | -------------------------------------------------------------------------------- /src/layouts/RouterBypass.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import './scss/main.scss' 2 | 3 | import { createApp } from 'vue' 4 | import App from './App.vue' 5 | import i18n from './i18n' 6 | import { createVuestic } from 'vuestic-ui' 7 | import { createGtm } from '@gtm-support/vue-gtm' 8 | 9 | import stores from './stores' 10 | import router from './router' 11 | import vuesticGlobalConfig from './services/vuestic-ui/global-config' 12 | 13 | const app = createApp(App) 14 | 15 | app.use(stores) 16 | app.use(router) 17 | app.use(i18n) 18 | app.use(createVuestic({ config: vuesticGlobalConfig })) 19 | 20 | if (import.meta.env.VITE_APP_GTM_ENABLED) { 21 | app.use( 22 | createGtm({ 23 | id: import.meta.env.VITE_APP_GTM_KEY, 24 | debug: false, 25 | vueRouter: router, 26 | }), 27 | ) 28 | } 29 | 30 | app.mount('#app') 31 | -------------------------------------------------------------------------------- /src/pages/404.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 31 | -------------------------------------------------------------------------------- /src/pages/admin/dashboard/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 33 | -------------------------------------------------------------------------------- /src/pages/admin/dashboard/DataSection.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 81 | -------------------------------------------------------------------------------- /src/pages/admin/dashboard/DataSectionItem.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 51 | -------------------------------------------------------------------------------- /src/pages/admin/dashboard/cards/MonthlyEarnings.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 66 | -------------------------------------------------------------------------------- /src/pages/admin/dashboard/cards/ProjectTable.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 66 | -------------------------------------------------------------------------------- /src/pages/admin/dashboard/cards/RegionRevenue.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 83 | 84 | 93 | -------------------------------------------------------------------------------- /src/pages/admin/dashboard/cards/RevenueByLocationMap.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 59 | 60 | 66 | -------------------------------------------------------------------------------- /src/pages/admin/dashboard/cards/RevenueReport.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 72 | -------------------------------------------------------------------------------- /src/pages/admin/dashboard/cards/RevenueReportChart.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 104 | 105 | 112 | -------------------------------------------------------------------------------- /src/pages/admin/dashboard/cards/Timeline.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 44 | -------------------------------------------------------------------------------- /src/pages/admin/dashboard/cards/YearlyBreakup.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 63 | -------------------------------------------------------------------------------- /src/pages/admin/pages/404PagesPage.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 47 | -------------------------------------------------------------------------------- /src/pages/auth/CheckTheEmail.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/pages/auth/Login.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 70 | -------------------------------------------------------------------------------- /src/pages/auth/RecoverPassword.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 35 | -------------------------------------------------------------------------------- /src/pages/billing/BillingPage.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 26 | -------------------------------------------------------------------------------- /src/pages/billing/Invoices.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 95 | -------------------------------------------------------------------------------- /src/pages/billing/modals/ChangeYourPaymentPlan.vue: -------------------------------------------------------------------------------- 1 | 24 | 43 | 44 | 49 | -------------------------------------------------------------------------------- /src/pages/billing/types.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicmaxco/vuestic-admin/fa573e20e8a4bf8faeccf2982a4e1a572767168d/src/pages/billing/types.ts -------------------------------------------------------------------------------- /src/pages/faq/FaqPage.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | -------------------------------------------------------------------------------- /src/pages/faq/data/navigationLinks.json: -------------------------------------------------------------------------------- 1 | { 2 | "Rising cost of living": [ 3 | { 4 | "name": "Fraud and Security" 5 | }, 6 | { 7 | "name": "Secure Key help" 8 | }, 9 | { 10 | "name": "Fraud and common scams" 11 | }, 12 | { 13 | "name": "What we need to keep you safe" 14 | }, 15 | { 16 | "name": "How we keep you safe" 17 | }, 18 | { 19 | "name": "How to keep yourself safe" 20 | } 21 | ], 22 | "Bank accounts": [ 23 | { 24 | "name": "General bank account help" 25 | }, 26 | { 27 | "name": "Switching to first direct" 28 | }, 29 | { 30 | "name": "Statements and balances" 31 | }, 32 | { 33 | "name": "Standing orders and Direct Debits" 34 | }, 35 | { 36 | "name": "Debit cards" 37 | }, 38 | { 39 | "name": "Overdrafts" 40 | }, 41 | { 42 | "name": "Managing personal details" 43 | } 44 | ], 45 | "Product support": [ 46 | { 47 | "name": "Personal loans help" 48 | }, 49 | { 50 | "name": "Credit card help" 51 | }, 52 | { 53 | "name": "Savings help" 54 | }, 55 | { 56 | "name": "Sharedealing help" 57 | }, 58 | { 59 | "name": "First Directory help" 60 | } 61 | ], 62 | "Mobile and Online Banking": [ 63 | { 64 | "name": "Register for Mobile and Online Banking" 65 | }, 66 | { 67 | "name": "Get help logging on" 68 | }, 69 | { 70 | "name": "Move your App to a new phone" 71 | } 72 | ], 73 | "Help with money worries": [ 74 | { 75 | "name": "Budgetting and money management" 76 | }, 77 | { 78 | "name": "Dealing with financial difficulty" 79 | } 80 | ], 81 | "Life events": [ 82 | { 83 | "name": "Help with bereavement" 84 | }, 85 | { 86 | "name": "Domestic and financial abuse" 87 | }, 88 | { 89 | "name": "Mental health and support" 90 | }, 91 | { 92 | "name": "Separation and your banking" 93 | }, 94 | { 95 | "name": "Someone else managing finances" 96 | } 97 | ] 98 | } 99 | -------------------------------------------------------------------------------- /src/pages/faq/data/popularCategories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "icon": "diversity_1", 5 | "name": "Getting Started", 6 | "intro": "Start using Service easily with our actionable tips." 7 | }, 8 | { 9 | "id": 2, 10 | "icon": "check_box", 11 | "name": "How-to Articles", 12 | "intro": "Check out ready workflows tailored to your needs." 13 | }, 14 | { 15 | "id": 3, 16 | "icon": "tv_signin", 17 | "name": "Billing and Account", 18 | "intro": "Adjust your subscription and limits to your liking." 19 | }, 20 | { 21 | "id": 4, 22 | "icon": "page_info", 23 | "name": "SEO", 24 | "intro": "Increase traffic and boost search rankings with the help of 20+ tools." 25 | }, 26 | { 27 | "id": 5, 28 | "icon": "currency_exchange", 29 | "name": "Advertising", 30 | "intro": "Research your competitors' advertising campaigns and launch your own." 31 | }, 32 | { 33 | "id": 6, 34 | "icon": "feed", 35 | "name": "Social Media", 36 | "intro": "Schedule, post, and track performance across all key social platforms." 37 | }, 38 | { 39 | "id": 7, 40 | "icon": "content_paste_go", 41 | "name": "Content Marketing", 42 | "intro": "Create a content plan, find gaps, and research, write and audit content." 43 | }, 44 | { 45 | "id": 8, 46 | "icon": "chart_data", 47 | "name": "Trends", 48 | "intro": "Analyze the market, benchmark against competitors,and follow emerging trends." 49 | }, 50 | { 51 | "id": 9, 52 | "icon": "manage_accounts", 53 | "name": "Management", 54 | "intro": "Keep all your marketing plans and activities under control. Automate reporting." 55 | } 56 | ] 57 | -------------------------------------------------------------------------------- /src/pages/faq/widgets/Categories.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 39 | -------------------------------------------------------------------------------- /src/pages/faq/widgets/Navigation.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 24 | -------------------------------------------------------------------------------- /src/pages/faq/widgets/RequestDemo.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 53 | -------------------------------------------------------------------------------- /src/pages/payments/PaymentsPage.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 33 | -------------------------------------------------------------------------------- /src/pages/payments/payment-system/PaymentSystem.stories.ts: -------------------------------------------------------------------------------- 1 | import PaymentSystem from './PaymentSystem.vue' 2 | 3 | export default { 4 | title: 'PaymentSystem', 5 | component: PaymentSystem, 6 | tags: ['autodocs'], 7 | } 8 | 9 | export const Default = () => ({ 10 | components: { PaymentSystem }, 11 | template: ` 12 | 13 |
14 | 15 | `, 16 | }) 17 | -------------------------------------------------------------------------------- /src/pages/payments/payment-system/PaymentSystem.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | -------------------------------------------------------------------------------- /src/pages/payments/payment-system/mastercard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicmaxco/vuestic-admin/fa573e20e8a4bf8faeccf2982a4e1a572767168d/src/pages/payments/payment-system/mastercard.png -------------------------------------------------------------------------------- /src/pages/payments/payment-system/visa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicmaxco/vuestic-admin/fa573e20e8a4bf8faeccf2982a4e1a572767168d/src/pages/payments/payment-system/visa.png -------------------------------------------------------------------------------- /src/pages/payments/types.ts: -------------------------------------------------------------------------------- 1 | export enum PaymentSystemType { 2 | Visa = 'visa', 3 | MasterCard = 'mastercard', 4 | } 5 | 6 | export const paymentSystemTypeOptions = Object.values(PaymentSystemType) 7 | 8 | export interface PaymentCard { 9 | id: string 10 | name: string 11 | isPrimary: boolean // show Primary badge 12 | paymentSystem: PaymentSystemType // Enum or union type for various payment systems 13 | cardNumberMasked: string // ****1679 14 | expirationDate: string // 09/24 15 | } 16 | 17 | export interface BillingAddress { 18 | id: string 19 | name: string 20 | isPrimary: boolean // show Primary badge 21 | street: string 22 | city: string 23 | state: string 24 | postalCode: string 25 | country: string 26 | } 27 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/billing-address/BillingAddressCreateModal.stories.ts: -------------------------------------------------------------------------------- 1 | import BillingAddressCreateModal from './BillingAddressCreateModal.vue' 2 | 3 | export default { 4 | components: { BillingAddressCreateModal }, 5 | title: 'BillingAddressCreateModal', 6 | component: BillingAddressCreateModal, 7 | tags: ['autodocs'], 8 | } 9 | 10 | export const Default = () => ({ 11 | components: { BillingAddressCreateModal }, 12 | data() { 13 | return { 14 | showModal: false, 15 | } 16 | }, 17 | template: ` 18 | 19 | Show modal 20 | 21 | 22 | `, 23 | }) 24 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/billing-address/BillingAddressCreateModal.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 44 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/billing-address/BillingAddressEdit.stories.ts: -------------------------------------------------------------------------------- 1 | import BillingAddressEdit from './BillingAddressEdit.vue' 2 | import { BillingAddress } from '../../types' 3 | 4 | export default { 5 | title: 'BillingAddressEdit', 6 | component: BillingAddressEdit, 7 | tags: ['autodocs'], 8 | } 9 | 10 | export const Default = () => ({ 11 | components: { BillingAddressEdit }, 12 | data() { 13 | return { 14 | lastEvent: '', 15 | billingAddress: { 16 | id: '1', 17 | name: 'Name', 18 | isPrimary: false, 19 | street: 'Ap #285-7193 Ullamcorper Avenue', 20 | city: 'Amesbury', 21 | state: 'HI', 22 | postalCode: '93373', 23 | country: 'US', 24 | } satisfies BillingAddress, 25 | } 26 | }, 27 | template: ` 28 | 34 |
35 |

Last event: {{ lastEvent }}

`, 36 | }) 37 | 38 | export const Empty = () => ({ 39 | components: { BillingAddressEdit }, 40 | data() { 41 | return { 42 | billingAddress: { 43 | id: '1', 44 | name: '', 45 | isPrimary: false, 46 | street: '', 47 | city: '', 48 | state: '', 49 | postalCode: '', 50 | country: '', 51 | } satisfies BillingAddress, 52 | } 53 | }, 54 | template: ` 55 | `, 59 | }) 60 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/billing-address/BillingAddressEdit.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 76 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/billing-address/BillingAddressList.stories.ts: -------------------------------------------------------------------------------- 1 | import BillingAddressList from './BillingAddressList.vue' 2 | 3 | export default { 4 | title: 'BillingAddressList', 5 | component: BillingAddressList, 6 | tags: ['autodocs'], 7 | } 8 | 9 | export const Default = () => ({ 10 | components: { BillingAddressList }, 11 | template: ` 12 | 13 | `, 14 | }) 15 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/billing-address/BillingAddressList.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 80 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/billing-address/BillingAddressListItem.stories.ts: -------------------------------------------------------------------------------- 1 | import BillingAddressListItem from './BillingAddressListItem.vue' 2 | import { BillingAddress } from '../../types' 3 | 4 | export default { 5 | title: 'BillingAddressListItem', 6 | component: BillingAddressListItem, 7 | tags: ['autodocs'], 8 | } 9 | 10 | export const Default = () => ({ 11 | components: { BillingAddressListItem }, 12 | data() { 13 | return { 14 | address: { 15 | id: '1', 16 | name: 'Home Address', 17 | isPrimary: false, 18 | street: 'Ap #285-7193 Ullamcorper Avenue', 19 | city: 'Amesbury', 20 | state: 'HI', 21 | postalCode: '93373', 22 | country: 'US', 23 | } satisfies BillingAddress, 24 | lastEvent: '___', 25 | } 26 | }, 27 | template: ` 28 | 33 |
34 |

Last event: {{ lastEvent }}

35 | `, 36 | }) 37 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/billing-address/BillingAddressListItem.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 35 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/billing-address/BillingAddressUpdateModal.stories.ts: -------------------------------------------------------------------------------- 1 | import BillingAddressUpdateModal from './BillingAddressUpdateModal.vue' 2 | import { BillingAddress } from '../../types' 3 | 4 | export default { 5 | components: { BillingAddressUpdateModal }, 6 | title: 'BillingAddressUpdateModal', 7 | component: BillingAddressUpdateModal, 8 | tags: ['autodocs'], 9 | } 10 | 11 | export const Default = () => ({ 12 | components: { BillingAddressUpdateModal }, 13 | data() { 14 | return { 15 | showModal: false, 16 | billingAddress: { 17 | id: '1', 18 | name: '', 19 | isPrimary: false, 20 | street: '', 21 | city: '', 22 | state: '', 23 | postalCode: '', 24 | country: '', 25 | } satisfies BillingAddress, 26 | } 27 | }, 28 | template: ` 29 | 30 | Show modal 31 | 32 | 33 | `, 34 | }) 35 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/billing-address/BillingAddressUpdateModal.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 38 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/my-cards/PaymentCardCreateModal.stories.ts: -------------------------------------------------------------------------------- 1 | import PaymentCardCreateModal from './PaymentCardCreateModal.vue' 2 | 3 | export default { 4 | components: { PaymentCardCreateModal }, 5 | title: 'PaymentCardCreateModal', 6 | component: PaymentCardCreateModal, 7 | tags: ['autodocs'], 8 | } 9 | 10 | export const Default = () => ({ 11 | components: { PaymentCardCreateModal }, 12 | data() { 13 | return { 14 | showModal: false, 15 | } 16 | }, 17 | template: ` 18 | 19 | Show modal 20 | 21 | 22 | `, 23 | }) 24 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/my-cards/PaymentCardCreateModal.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 37 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/my-cards/PaymentCardEdit.stories.ts: -------------------------------------------------------------------------------- 1 | import PaymentCardEdit from './PaymentCardEdit.vue' 2 | import { PaymentSystemType, PaymentCard } from '../../types' 3 | 4 | export default { 5 | title: 'PaymentCardEdit', 6 | component: PaymentCardEdit, 7 | tags: ['autodocs'], 8 | } 9 | 10 | export const Default = () => ({ 11 | components: { PaymentCardEdit }, 12 | data() { 13 | return { 14 | lastEvent: '', 15 | paymentCard: { 16 | id: '1', 17 | name: 'Main card', 18 | isPrimary: true, 19 | paymentSystem: PaymentSystemType.Visa, 20 | cardNumberMasked: '****1679', 21 | expirationDate: '09/24', 22 | } satisfies PaymentCard, 23 | } 24 | }, 25 | template: ` 26 | 32 |
33 |

Last event: {{ lastEvent }}

`, 34 | }) 35 | 36 | export const Empty = () => ({ 37 | components: { PaymentCardEdit }, 38 | data() { 39 | return { 40 | paymentCard: { 41 | id: '', 42 | name: '', 43 | isPrimary: false, 44 | paymentSystem: PaymentSystemType.Visa, 45 | cardNumberMasked: '', 46 | expirationDate: '', 47 | } satisfies PaymentCard, 48 | } 49 | }, 50 | template: ` 51 | `, 55 | }) 56 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/my-cards/PaymentCardEdit.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 79 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/my-cards/PaymentCardList.stories.ts: -------------------------------------------------------------------------------- 1 | import PaymentCardList from './PaymentCardList.vue' 2 | 3 | export default { 4 | title: 'PaymentCardList', 5 | component: PaymentCardList, 6 | tags: ['autodocs'], 7 | } 8 | 9 | export const Default = () => ({ 10 | components: { PaymentCardList }, 11 | template: ` 12 | 13 | `, 14 | }) 15 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/my-cards/PaymentCardList.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 79 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/my-cards/PaymentCardListItem.stories.ts: -------------------------------------------------------------------------------- 1 | import CardListItem from './PaymentCardListItem.vue' 2 | import { PaymentSystemType, PaymentCard } from '../../types' 3 | 4 | export default { 5 | title: 'CardListItem', 6 | component: CardListItem, 7 | tags: ['autodocs'], 8 | } 9 | 10 | export const Default = () => ({ 11 | components: { CardListItem }, 12 | data() { 13 | return { 14 | card: { 15 | id: '1', 16 | name: 'Main card', 17 | isPrimary: true, 18 | paymentSystem: PaymentSystemType.Visa, 19 | cardNumberMasked: '****1679', 20 | expirationDate: '09/24', 21 | } satisfies PaymentCard, 22 | lastEvent: '___', 23 | } 24 | }, 25 | template: ` 26 | 31 |
32 |

Last event: {{ lastEvent }}

33 | `, 34 | }) 35 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/my-cards/PaymentCardListItem.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 43 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/my-cards/PaymentCardUpdateModal.stories.ts: -------------------------------------------------------------------------------- 1 | import PaymentCardUpdateModal from './PaymentCardUpdateModal.vue' 2 | import { PaymentSystemType, PaymentCard } from '../../types' 3 | 4 | export default { 5 | components: { PaymentCardUpdateModal }, 6 | title: 'PaymentCardUpdateModal', 7 | component: PaymentCardUpdateModal, 8 | tags: ['autodocs'], 9 | } 10 | 11 | export const Default = () => ({ 12 | components: { PaymentCardUpdateModal }, 13 | data() { 14 | return { 15 | showModal: false, 16 | paymentCard: { 17 | id: '1', 18 | name: 'Main card', 19 | isPrimary: true, 20 | paymentSystem: PaymentSystemType.Visa, 21 | cardNumberMasked: '****1679', 22 | expirationDate: '09/24', 23 | } satisfies PaymentCard, 24 | } 25 | }, 26 | template: ` 27 | 28 | Show modal 29 | 30 | 31 | `, 32 | }) 33 | -------------------------------------------------------------------------------- /src/pages/payments/widgets/my-cards/PaymentCardUpdateModal.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 33 | -------------------------------------------------------------------------------- /src/pages/preferences/Preferences.vue: -------------------------------------------------------------------------------- 1 | 14 | 25 | -------------------------------------------------------------------------------- /src/pages/preferences/modals/EditNameModal.vue: -------------------------------------------------------------------------------- 1 | 21 | 46 | 47 | 53 | -------------------------------------------------------------------------------- /src/pages/preferences/preferences-header/PreferencesHeader.vue: -------------------------------------------------------------------------------- 1 | 11 | 16 | -------------------------------------------------------------------------------- /src/pages/preferences/styles.ts: -------------------------------------------------------------------------------- 1 | export const buttonStyles = { 2 | '--va-button-font-size': '14px', 3 | '--va-button-line-height': '20px', 4 | } 5 | -------------------------------------------------------------------------------- /src/pages/pricing-plans/options.ts: -------------------------------------------------------------------------------- 1 | export type PricingPlans = { 2 | title: string 3 | model: string 4 | badges?: string[] 5 | description: string 6 | price: number 7 | priceMonth: number 8 | features: Feature[] 9 | } 10 | 11 | type Feature = { 12 | description: string 13 | isAvailable: boolean 14 | } 15 | 16 | const features = [ 17 | 'Up to 10 Active Users', 18 | 'Up to 30 Project Integrations', 19 | 'Analytics Module', 20 | 'Finance Module', 21 | 'Accounting Module', 22 | 'Network Platform', 23 | 'Unlimited Cloud Spase', 24 | ] 25 | 26 | export const pricingPlans: PricingPlans[] = [ 27 | { 28 | title: 'Startup', 29 | model: 'Startup', 30 | description: 'Optimal for 10+ team size and new startup', 31 | price: 39, 32 | priceMonth: 5, 33 | features: features.map((d, i) => ({ description: d, isAvailable: i < 3 })), 34 | }, 35 | { 36 | title: 'Advanced', 37 | model: 'Advanced', 38 | description: 'Optimal for 100+ team size and grown company', 39 | price: 339, 40 | priceMonth: 35, 41 | features: features.map((d, i) => ({ description: d, isAvailable: i < 5 })), 42 | badges: ['Popular choice'], 43 | }, 44 | { 45 | title: 'Enterprise', 46 | model: 'Enterprise', 47 | description: 'Optimal for 1000+ team and enterpise', 48 | price: 999, 49 | priceMonth: 100, 50 | features: features.map((d) => ({ description: d, isAvailable: true })), 51 | }, 52 | ] 53 | -------------------------------------------------------------------------------- /src/pages/pricing-plans/styles.ts: -------------------------------------------------------------------------------- 1 | export const badgeStyles = { 2 | '--va-badge-text-wrapper-line-height': '14px', 3 | '--va-badge-text-wrapper-letter-spacing': '0.4px', 4 | '--va-badge-text-wrapper-border': 'solid 1px', 5 | '--va-badge-font-size': '9px', 6 | } 7 | 8 | export const selectButtonStyles = { 9 | '--va-button-content-py': '10px', 10 | '--va-button-content-px': '16px', 11 | '--va-button-font-size': '18px', 12 | '--va-button-line-height': '26px', 13 | } 14 | -------------------------------------------------------------------------------- /src/pages/projects/components/ProjectStatusBadge.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 23 | -------------------------------------------------------------------------------- /src/pages/projects/composables/useProjectStatusColor.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicmaxco/vuestic-admin/fa573e20e8a4bf8faeccf2982a4e1a572767168d/src/pages/projects/composables/useProjectStatusColor.ts -------------------------------------------------------------------------------- /src/pages/projects/composables/useProjectUsers.ts: -------------------------------------------------------------------------------- 1 | import { useUsers } from '../../users/composables/useUsers' 2 | import { Project } from '../types' 3 | 4 | export function useProjectUsers() { 5 | const { users } = useUsers() 6 | 7 | const getUserById = (userId: string) => { 8 | return users.value.find(({ id }) => userId === id) 9 | } 10 | 11 | const avatarColor = (userName: string) => { 12 | const colors = ['primary', '#FFD43A', '#ADFF00', '#262824', 'danger'] 13 | const index = userName.charCodeAt(0) % colors.length 14 | return colors[index] 15 | } 16 | 17 | const getTeamOptions = (team: Project['team']) => { 18 | return team.reduce( 19 | (acc, userId) => { 20 | const user = getUserById(userId) 21 | 22 | if (user) { 23 | acc.push({ 24 | label: user.fullname, 25 | src: user.avatar, 26 | fallbackText: user.fullname[0], 27 | color: avatarColor(user.fullname), 28 | }) 29 | } 30 | 31 | return acc 32 | }, 33 | [] as Record[], 34 | ) 35 | } 36 | 37 | return { 38 | users, 39 | getUserById, 40 | getTeamOptions, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/pages/projects/composables/useProjects.ts: -------------------------------------------------------------------------------- 1 | import { Ref, ref, unref, computed } from 'vue' 2 | import { Sorting, Pagination } from '../../../data/pages/projects' 3 | import { Project } from '../types' 4 | import { useProjectsStore } from '../../../stores/projects' 5 | import { useProjectUsers } from './useProjectUsers' 6 | 7 | const makePaginationRef = () => ref({ page: 1, perPage: 10, total: 0 }) 8 | const makeSortingRef = () => ref({ sortBy: 'created_at', sortingOrder: 'desc' }) 9 | 10 | export const useProjects = (options?: { sorting?: Ref; pagination?: Ref }) => { 11 | const isLoading = ref(false) 12 | const projectsStore = useProjectsStore() 13 | const { getUserById } = useProjectUsers() 14 | 15 | const { sorting = makeSortingRef(), pagination = makePaginationRef() } = options ?? {} 16 | 17 | const fetch = async () => { 18 | isLoading.value = true 19 | await projectsStore.getAll({ 20 | sorting: unref(sorting), 21 | pagination: unref(pagination), 22 | }) 23 | pagination.value = projectsStore.pagination 24 | 25 | isLoading.value = false 26 | } 27 | 28 | const projects = computed(() => { 29 | const paginated = projectsStore.items.slice( 30 | (pagination.value.page - 1) * pagination.value.perPage, 31 | pagination.value.page * pagination.value.perPage, 32 | ) 33 | 34 | const getSortItem = (obj: any, sortBy: Sorting['sortBy']) => { 35 | if (sortBy === 'project_owner') { 36 | return getUserById(obj.project_owner)?.fullname 37 | } 38 | 39 | if (sortBy === 'team') { 40 | return obj.team.map((user: any) => getUserById(user)?.fullname || '').join(', ') 41 | } 42 | 43 | if (sortBy === 'created_at') { 44 | return new Date(obj[sortBy]) 45 | } 46 | 47 | return obj[sortBy] 48 | } 49 | 50 | if (sorting.value.sortBy && sorting.value.sortingOrder) { 51 | paginated.sort((a, b) => { 52 | a = getSortItem(a, sorting.value.sortBy!) 53 | b = getSortItem(b, sorting.value.sortBy!) 54 | 55 | if (a < b) { 56 | return sorting.value.sortingOrder === 'asc' ? -1 : 1 57 | } 58 | if (a > b) { 59 | return sorting.value.sortingOrder === 'asc' ? 1 : -1 60 | } 61 | return 0 62 | }) 63 | } 64 | 65 | return paginated 66 | }) 67 | 68 | fetch() 69 | 70 | return { 71 | isLoading, 72 | 73 | projects, 74 | 75 | fetch, 76 | 77 | async add(project: Omit) { 78 | isLoading.value = true 79 | await projectsStore.add(project) 80 | await fetch() 81 | isLoading.value = false 82 | }, 83 | 84 | async update(project: Project) { 85 | isLoading.value = true 86 | await projectsStore.update(project) 87 | await fetch() 88 | isLoading.value = false 89 | }, 90 | 91 | async remove(project: Project) { 92 | isLoading.value = true 93 | await projectsStore.remove(project) 94 | await fetch() 95 | isLoading.value = false 96 | }, 97 | 98 | pagination, 99 | sorting, 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/pages/projects/types.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../users/types' 2 | 3 | export type UUID = `${string}-${string}-${string}-${string}-${string}` 4 | 5 | export type Project = { 6 | id: UUID 7 | project_name: string 8 | project_owner: User['id'] 9 | team: User['id'][] 10 | status: 'important' | 'completed' | 'archived' | 'in progress' 11 | created_at: string 12 | } 13 | 14 | export type EmptyProject = Omit & { 15 | project_owner: Project['project_owner'] | undefined 16 | status: Project['status'] | undefined 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/projects/widgets/ProjectCards.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 60 | -------------------------------------------------------------------------------- /src/pages/settings/Settings.vue: -------------------------------------------------------------------------------- 1 | 24 | 29 | -------------------------------------------------------------------------------- /src/pages/settings/language-switcher/LanguageSwitcher.vue: -------------------------------------------------------------------------------- 1 | 9 | 47 | -------------------------------------------------------------------------------- /src/pages/settings/notifications/Notifications.vue: -------------------------------------------------------------------------------- 1 | 15 | 20 | -------------------------------------------------------------------------------- /src/pages/settings/theme-switcher/ThemeSwitcher.vue: -------------------------------------------------------------------------------- 1 | 4 | 29 | -------------------------------------------------------------------------------- /src/pages/users/types.ts: -------------------------------------------------------------------------------- 1 | export type UserRole = 'admin' | 'user' | 'owner' 2 | 3 | export type UUID = `${string}-${string}-${string}-${string}-${string}` 4 | 5 | export type User = { 6 | id: UUID 7 | fullname: string 8 | email: string 9 | username: string 10 | role: UserRole 11 | avatar: string 12 | projects: UUID[] 13 | notes: string 14 | active: boolean 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/users/widgets/UserAvatar.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 46 | -------------------------------------------------------------------------------- /src/scss/icon-fonts/index.scss: -------------------------------------------------------------------------------- 1 | // These fonts were originally provided by http://weloveiconfonts.com. 2 | // We decided to add these into package after https ceased to work due to lack of support on their side. 3 | @import 'vuestic-icons/vuestic-icons'; 4 | -------------------------------------------------------------------------------- /src/scss/icon-fonts/vuestic-icons/vuestic-icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicmaxco/vuestic-admin/fa573e20e8a4bf8faeccf2982a4e1a572767168d/src/scss/icon-fonts/vuestic-icons/vuestic-icons.eot -------------------------------------------------------------------------------- /src/scss/icon-fonts/vuestic-icons/vuestic-icons.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Vuestic Icons'; 3 | font-weight: normal; 4 | font-style: normal; 5 | src: url('vuestic-icons.eot'); 6 | src: 7 | url('/src/scss/icon-fonts/vuestic-icons/vuestic-icons.eot?#iefix?local') format('eot'), 8 | url('/src/scss/icon-fonts/vuestic-icons/vuestic-icons.woff?url') format('woff'), 9 | url('/src/scss/icon-fonts/vuestic-icons/vuestic-icons.ttf') format('truetype'), 10 | url('/src/scss/icon-fonts/vuestic-icons/vuestic-icons.svg#vuestic-icons') format('svg'); 11 | } 12 | 13 | .vuestic-iconset { 14 | line-height: 1; 15 | } 16 | 17 | .vuestic-iconset::before { 18 | display: inline-block; 19 | font-family: 'Vuestic Icons'; 20 | font-style: normal; 21 | font-weight: normal; 22 | line-height: 1; 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | .vuestic-iconset-comments::before { 28 | content: '\0041'; 29 | } 30 | 31 | .vuestic-iconset-components::before { 32 | content: '\0042'; 33 | } 34 | 35 | .vuestic-iconset-dashboard::before { 36 | content: '\0043'; 37 | } 38 | 39 | .vuestic-iconset-extras::before { 40 | content: '\0044'; 41 | } 42 | 43 | .vuestic-iconset-files::before { 44 | content: '\0045'; 45 | } 46 | 47 | .vuestic-iconset-forms::before { 48 | content: '\0046'; 49 | } 50 | 51 | .vuestic-iconset-graph::before { 52 | content: '\0047'; 53 | } 54 | 55 | .vuestic-iconset-auth::before { 56 | content: '\0048'; 57 | } 58 | 59 | .vuestic-iconset-image::before { 60 | content: '\0049'; 61 | } 62 | 63 | .vuestic-iconset-maps::before { 64 | content: '\004a'; 65 | } 66 | 67 | .vuestic-iconset-music::before { 68 | content: '\004b'; 69 | } 70 | 71 | .vuestic-iconset-settings::before { 72 | content: '\004c'; 73 | } 74 | 75 | .vuestic-iconset-statistics::before { 76 | content: '\004d'; 77 | } 78 | 79 | .vuestic-iconset-tables::before { 80 | content: '\004e'; 81 | } 82 | 83 | .vuestic-iconset-time::before { 84 | content: '\004f'; 85 | } 86 | 87 | .vuestic-iconset-ui-elements::before { 88 | content: '\0050'; 89 | } 90 | 91 | .vuestic-iconset-user::before { 92 | content: '\0051'; 93 | } 94 | 95 | .vuestic-iconset-video::before { 96 | content: '\0052'; 97 | } 98 | -------------------------------------------------------------------------------- /src/scss/icon-fonts/vuestic-icons/vuestic-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicmaxco/vuestic-admin/fa573e20e8a4bf8faeccf2982a4e1a572767168d/src/scss/icon-fonts/vuestic-icons/vuestic-icons.ttf -------------------------------------------------------------------------------- /src/scss/icon-fonts/vuestic-icons/vuestic-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicmaxco/vuestic-admin/fa573e20e8a4bf8faeccf2982a4e1a572767168d/src/scss/icon-fonts/vuestic-icons/vuestic-icons.woff -------------------------------------------------------------------------------- /src/scss/main.scss: -------------------------------------------------------------------------------- 1 | @import 'tailwind'; 2 | @import 'vuestic'; 3 | @import 'icon-fonts/index'; 4 | 5 | body { 6 | @apply text-regularMedium; 7 | 8 | // TODO Move to some other place so that it's more elegant. 9 | --va-checkbox-font-size: 0.875rem; 10 | --va-card-box-shadow: none; // TODO Remove after https://github.com/epicmaxco/vuestic-ui/issues/3964 11 | --va-card-padding: 1rem; 12 | --va-font-family: 'Inter', sans-serif; 13 | } 14 | 15 | code, 16 | kbd, 17 | samp, 18 | pre { 19 | font-family: monospace; 20 | font-size: 1em; 21 | } 22 | 23 | .ellipsis { 24 | overflow: hidden; 25 | text-overflow: ellipsis; 26 | white-space: nowrap; 27 | } 28 | 29 | .page-title { 30 | @apply text-[32px] md:text-5xl font-bold leading-9 md:leading-[56px] max-sm:mt-6 mb-6 md:mb-4; 31 | } 32 | 33 | .h1 { 34 | @apply text-[32px] md:text-5xl font-bold leading-9 md:leading-[56px] max-sm:mt-6 mb-6 md:mb-4; 35 | } 36 | 37 | .h3, 38 | .page-sub-title { 39 | @apply text-2xl font-bold leading-[30px]; 40 | } 41 | 42 | .h5 { 43 | @apply font-bold leading-tight; 44 | } 45 | 46 | .block-title { 47 | @apply text-2xl font-bold mb-2; 48 | } 49 | 50 | .pricing-plan-card-title { 51 | @apply text-[28px] md:text-[32px] leading-10 font-bold; 52 | } 53 | 54 | .text-regular-small { 55 | font-size: 0.8125rem; 56 | line-height: 1rem; 57 | } 58 | -------------------------------------------------------------------------------- /src/scss/tailwind.scss: -------------------------------------------------------------------------------- 1 | @layer tailwind.base { 2 | @import 'tailwindcss/base'; 3 | } 4 | @tailwind components; 5 | @tailwind utilities; 6 | -------------------------------------------------------------------------------- /src/scss/vuestic.scss: -------------------------------------------------------------------------------- 1 | @import 'vuestic-ui/dist/styles/css-variables.css'; 2 | @import 'vuestic-ui/dist/styles/essential.css'; 3 | @import 'vuestic-ui/dist/styles/theme.css'; 4 | @import 'vuestic-ui/dist/styles/typography.css'; 5 | 6 | .va-input, 7 | .va-select { 8 | width: 100%; 9 | } 10 | 11 | :root { 12 | --va-modal-padding-top: 1rem; 13 | --va-modal-padding-right: 1rem; 14 | --va-modal-padding-bottom: 1rem; 15 | --va-modal-padding-left: 1rem; 16 | } 17 | 18 | .va-modal { 19 | line-height: 20px; 20 | 21 | &__message { 22 | margin-bottom: 16px; 23 | } 24 | 25 | &__dialog { 26 | border-radius: 8px; 27 | 28 | @media (min-width: 640px) { 29 | border-radius: 4px; 30 | } 31 | } 32 | } 33 | 34 | .va-input { 35 | &-label { 36 | font-size: 9px; 37 | line-height: 14px; 38 | letter-spacing: 0.4px; 39 | min-height: 14px; 40 | 41 | &__required-mark { 42 | font-size: 13px; 43 | } 44 | } 45 | 46 | &-wrapper { 47 | &__size-keeper { 48 | height: auto; 49 | } 50 | 51 | &__label--outer { 52 | margin-bottom: 4px; 53 | } 54 | 55 | &__field { 56 | padding: 8px 12px; 57 | } 58 | } 59 | } 60 | 61 | .va-sidebar { 62 | &__item__content { 63 | min-height: 44px; 64 | } 65 | } 66 | 67 | .va-select { 68 | &--small { 69 | .va-input-wrapper__field { 70 | padding: 0 0.25rem; 71 | } 72 | } 73 | 74 | &-anchor__input { 75 | flex: unset; 76 | } 77 | } 78 | 79 | .va-card { 80 | &__title { 81 | padding-bottom: calc(var(--va-card-padding) / 2) !important; 82 | } 83 | } 84 | 85 | .va-data-table { 86 | .va-inner-loading__spinner { 87 | margin-top: 32px; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/services/api.ts: -------------------------------------------------------------------------------- 1 | const apiBaseUrl = import.meta.env.VITE_API_BASE_URL 2 | 3 | export default { 4 | allUsers: () => `${apiBaseUrl}/users`, 5 | user: (id: string) => `${apiBaseUrl}/users/${id}`, 6 | users: ({ page, pageSize }: { page: number; pageSize: number }) => 7 | `${apiBaseUrl}/users/?page=${page}&pageSize=${pageSize}`, 8 | allProjects: () => `${apiBaseUrl}/projects`, 9 | project: (id: string) => `${apiBaseUrl}/projects/${id}`, 10 | projects: ({ page, pageSize }: { page: number; pageSize: number }) => 11 | `${apiBaseUrl}/projects/?page=${page}&pageSize=${pageSize}`, 12 | avatars: () => `${apiBaseUrl}/avatars`, 13 | } 14 | -------------------------------------------------------------------------------- /src/services/toCSV.ts: -------------------------------------------------------------------------------- 1 | export const toCSV = (data: Record[]) => { 2 | const headers = Object.keys(data[0]) 3 | const csv = [ 4 | headers.join(','), 5 | ...data.map((row) => headers.map((fieldName) => JSON.stringify(row[fieldName])).join(',')), 6 | ].join('\r\n') 7 | return csv 8 | } 9 | 10 | export const downloadAsCSV = (data: Record[], filename: string) => { 11 | const csv = toCSV(data) 12 | 13 | const blob = new Blob([csv], { type: 'text/csv' }) 14 | 15 | const link = document.createElement('a') 16 | link.href = window.URL.createObjectURL(blob) 17 | link.download = filename 18 | link.click() 19 | } 20 | -------------------------------------------------------------------------------- /src/services/utils.ts: -------------------------------------------------------------------------------- 1 | export const sleep = (ms = 0) => { 2 | return new Promise((resolve) => setTimeout(resolve, ms)) 3 | } 4 | 5 | /** Validation */ 6 | export const validators = { 7 | email: (v: string) => { 8 | const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ 9 | return pattern.test(v) || 'Please enter a valid email address' 10 | }, 11 | required: (v: any) => !!v || 'This field is required', 12 | } 13 | -------------------------------------------------------------------------------- /src/services/vuestic-ui/global-config.ts: -------------------------------------------------------------------------------- 1 | import iconsConfig from './icons-config/icons-config' 2 | import colors from './themes' 3 | import { defineVuesticConfig } from 'vuestic-ui' 4 | 5 | export default defineVuesticConfig({ 6 | colors, 7 | icons: iconsConfig, 8 | breakpoint: { 9 | enabled: true, 10 | bodyClass: true, 11 | thresholds: { 12 | xs: 0, 13 | sm: 320, 14 | md: 640, 15 | lg: 1024, 16 | xl: 1440, 17 | }, 18 | }, 19 | components: { 20 | VaIcon: { 21 | sizesConfig: { 22 | defaultSize: 19, 23 | sizes: { 24 | small: 14, 25 | medium: 19, 26 | large: 26, 27 | }, 28 | }, 29 | }, 30 | VaModal: { 31 | mobileFullscreen: false, 32 | maxHeight: 'calc(100% - 2rem)', 33 | }, 34 | VaPagination: { 35 | activeButtonProps: { 36 | preset: 'primary', 37 | }, 38 | }, 39 | VaDataTable: { 40 | disableClientSideSorting: true, 41 | }, 42 | presets: { 43 | VaSelect: { 44 | small: { 45 | class: 'va-select--small', 46 | keepAnchorWidth: false, 47 | placement: 'bottom-end', 48 | width: 'min(100%, 150px)', 49 | style: 50 | '--va-input-wrapper-min-height: 24px; --va-input-wrapper-border-radius: 2px; --va-input-wrapper-width: 100px;', 51 | }, 52 | }, 53 | }, 54 | }, 55 | }) 56 | -------------------------------------------------------------------------------- /src/services/vuestic-ui/icons-config/aliases.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | name: 'angle_down', 4 | to: 'fa4-angle-down', 5 | }, 6 | { 7 | name: 'angle_up', 8 | to: 'fa4-angle-up', 9 | }, 10 | { 11 | name: 'bell', 12 | to: 'fa4-bell', 13 | }, 14 | { 15 | name: 'bell_slash', 16 | to: 'fa4-bell-slash', 17 | }, 18 | { 19 | name: 'cogs', 20 | to: 'fa4-cogs', 21 | }, 22 | { 23 | name: 'envelope', 24 | to: 'fa4-envelope', 25 | }, 26 | { 27 | name: 'eye', 28 | to: 'fa4-eye', 29 | }, 30 | { 31 | name: 'gear', 32 | to: 'fa4-gear', 33 | }, 34 | { 35 | name: 'map', 36 | to: 'fa4-map', 37 | }, 38 | { 39 | name: 'map_marker', 40 | to: 'fa4-map-marker', 41 | }, 42 | { 43 | name: 'music', 44 | to: 'fa4-music', 45 | }, 46 | { 47 | name: 'print', 48 | to: 'fa4-print', 49 | }, 50 | { 51 | name: 'refresh', 52 | to: 'fa4-refresh', 53 | }, 54 | { 55 | name: 'search', 56 | to: 'fa4-search', 57 | }, 58 | { 59 | name: 'mars', 60 | to: 'fa4-mars', 61 | }, 62 | { 63 | name: 'venus', 64 | to: 'fa4-venus', 65 | }, 66 | { 67 | name: 'volume_off', 68 | to: 'fa4-volume-off', 69 | }, 70 | { 71 | name: 'volume_up', 72 | to: 'fa4-volume-up', 73 | }, 74 | { 75 | name: 'github', 76 | to: 'fa4-github', 77 | }, 78 | { 79 | name: 'md_close', 80 | to: 'ion-md-close', 81 | }, 82 | { 83 | name: 'images', 84 | to: 'ion-md-images', 85 | }, 86 | { 87 | name: 'list', 88 | to: 'ion-md-list', 89 | }, 90 | { 91 | name: 'musical_notes', 92 | to: 'ion-md-musical-notes', 93 | }, 94 | { 95 | name: 'star_outline', 96 | to: 'ion-md-star-outline', 97 | }, 98 | { 99 | name: 'grid', 100 | to: 'ion-md-grid', 101 | }, 102 | { 103 | name: 'help', 104 | to: 'ion-md-help', 105 | }, 106 | { 107 | name: 'key', 108 | to: 'ion-md-key', 109 | }, 110 | ] 111 | -------------------------------------------------------------------------------- /src/services/vuestic-ui/icons-config/icons-config.ts: -------------------------------------------------------------------------------- 1 | import { createIconsConfig } from 'vuestic-ui' 2 | import aliases from './aliases' 3 | 4 | export default createIconsConfig({ 5 | aliases, 6 | fonts: [ 7 | { 8 | name: 'fa4-{code}', 9 | resolve: ({ code }) => ({ class: `fa4 fa fa-${code}` }), 10 | }, 11 | { 12 | name: 'ion-{font}-{code}', 13 | resolve: ({ font, code }) => ({ 14 | class: `icon ion-${font}-${code}`, 15 | }), 16 | }, 17 | { 18 | name: 'vuestic-iconset-{code}', 19 | resolve: ({ code }) => ({ class: `vuestic-iconset vuestic-iconset-${code}` }), 20 | }, 21 | { 22 | name: 'flag-icon-{code} {size}', 23 | resolve: ({ code, size }) => ({ class: `fi fi-${code} fi-size-${size}`, tag: 'span' }), 24 | }, 25 | { 26 | name: /(brandico|entypo|fa|fontelico|glyphicon|iconicstroke|maki|openwebicons)-(.*)/, 27 | resolveFromRegex: (font, code) => ({ class: `${font} ${font}-${code}` }), 28 | }, 29 | { 30 | name: 'material-icons-{code}', 31 | resolve: ({ code }) => ({ to: code }), 32 | }, 33 | { 34 | name: 'mso-{content}', 35 | class: 'material-symbols-outlined', 36 | resolve: ({ content }) => ({ content: content }), 37 | }, 38 | ], 39 | }) 40 | -------------------------------------------------------------------------------- /src/services/vuestic-ui/themes.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | presets: { 3 | light: { 4 | backgroundPrimary: '#F4F6F8', 5 | backgroundSecondary: '#FFFFFF', 6 | backgroundCardPrimary: '#F7F9F9', 7 | backgroundCardSecondary: '#ECFDE6', 8 | success: '#228200', 9 | info: '#158DE3', 10 | danger: '#E42222', 11 | warning: '#FFD43A', 12 | }, 13 | dark: { 14 | backgroundCardPrimary: '#111827', 15 | backgroundCardSecondary: '#0f172a', 16 | }, 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /src/stores/billing-addresses.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { sleep } from '../services/utils' 3 | import { BillingAddress } from '../pages/payments/types' // adjust the import path accordingly 4 | 5 | // Simulated fetch function 6 | const fetchBillingAddresses = async () => { 7 | await sleep(1000) 8 | return [ 9 | { 10 | id: '1', 11 | name: 'Home address', 12 | isPrimary: true, 13 | street: 'Ap #285-7193 Ullamcorper Avenue', 14 | city: 'Amesbury', 15 | state: 'HI', 16 | postalCode: '93373', 17 | country: 'US', 18 | }, 19 | { 20 | id: '2', 21 | name: 'Office address', 22 | isPrimary: false, 23 | street: 'P.O. Box 847, 8011 Nisl St.', 24 | city: 'Morgantown', 25 | state: 'IN', 26 | postalCode: '46160', 27 | country: 'US', 28 | }, 29 | { 30 | id: '3', 31 | name: 'Vacation home', 32 | isPrimary: false, 33 | street: '883-2699 Egestas Rd.', 34 | city: 'Frederick', 35 | state: 'NE', 36 | postalCode: '20620', 37 | country: 'US', 38 | }, 39 | ] as BillingAddress[] 40 | } 41 | 42 | export const useBillingAddressesStore = defineStore({ 43 | id: 'billingAddresses', 44 | state: () => ({ 45 | billingAddresses: [] as BillingAddress[], 46 | loading: false, 47 | }), 48 | getters: { 49 | allBillingAddresses: (state) => state.billingAddresses, 50 | }, 51 | actions: { 52 | async load() { 53 | this.loading = true 54 | this.billingAddresses = await fetchBillingAddresses() 55 | this.loading = false 56 | }, 57 | create(address: BillingAddress) { 58 | this.billingAddresses.unshift(address) 59 | }, 60 | update(address: BillingAddress) { 61 | const index = this.billingAddresses.findIndex((existingCard) => existingCard.id === address.id) 62 | if (index !== -1) { 63 | this.billingAddresses.splice(index, 1, address) 64 | } 65 | }, 66 | remove(addressId: string) { 67 | this.billingAddresses = this.billingAddresses.filter((address) => address.id !== addressId) 68 | }, 69 | }, 70 | }) 71 | -------------------------------------------------------------------------------- /src/stores/global-store.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | export const useGlobalStore = defineStore('global', { 4 | state: () => { 5 | return { 6 | isSidebarMinimized: false, 7 | } 8 | }, 9 | 10 | actions: { 11 | toggleSidebar() { 12 | this.isSidebarMinimized = !this.isSidebarMinimized 13 | }, 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /src/stores/index.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia' 2 | 3 | export default createPinia() 4 | -------------------------------------------------------------------------------- /src/stores/notifications.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | export const useNotificationsStore = defineStore('notifications', { 4 | state: () => { 5 | return { 6 | notifications: { 7 | searchingForAJob: { 8 | name: 'Searching for a job', 9 | isEnabled: true, 10 | }, 11 | hiringSomeone: { 12 | name: 'Hiring someone', 13 | isEnabled: false, 14 | }, 15 | connectingWithOthers: { 16 | name: 'Connecting with others', 17 | isEnabled: true, 18 | }, 19 | postingAndCommenting: { 20 | name: 'Posting and commenting', 21 | isEnabled: true, 22 | }, 23 | messaging: { 24 | name: 'Messaging', 25 | isEnabled: true, 26 | }, 27 | groups: { 28 | name: 'Groups', 29 | isEnabled: false, 30 | }, 31 | pages: { 32 | name: 'Pages', 33 | isEnabled: true, 34 | }, 35 | attendingEvents: { 36 | name: 'Attending events', 37 | isEnabled: true, 38 | }, 39 | newsAndReports: { 40 | name: 'News and reports', 41 | isEnabled: false, 42 | }, 43 | updatingYourProfile: { 44 | name: 'Updating your profile', 45 | isEnabled: true, 46 | }, 47 | verifications: { 48 | name: 'Verifications', 49 | isEnabled: true, 50 | }, 51 | }, 52 | } 53 | }, 54 | }) 55 | -------------------------------------------------------------------------------- /src/stores/payment-cards.ts: -------------------------------------------------------------------------------- 1 | // src/stores/cards.ts 2 | 3 | import { defineStore } from 'pinia' 4 | import { sleep } from '../services/utils' 5 | import { PaymentSystemType, PaymentCard } from '../pages/payments/types' // adjust the import path accordingly 6 | 7 | // Simulated fetch function 8 | const fetchPaymentCards = async () => { 9 | await sleep(1000) 10 | return [ 11 | { 12 | id: '1', 13 | name: 'Main card', 14 | isPrimary: true, 15 | paymentSystem: PaymentSystemType.Visa, 16 | cardNumberMasked: '****1679', 17 | expirationDate: '0924', 18 | }, 19 | { 20 | id: '2', 21 | name: 'Online shopping', 22 | isPrimary: false, 23 | paymentSystem: PaymentSystemType.MasterCard, 24 | cardNumberMasked: '****8921', 25 | expirationDate: '1123', 26 | }, 27 | { 28 | id: '3', 29 | name: 'Backup Visa', 30 | isPrimary: false, 31 | paymentSystem: PaymentSystemType.MasterCard, 32 | cardNumberMasked: '****4523', 33 | expirationDate: '1222', 34 | }, 35 | ] 36 | } 37 | 38 | export const usePaymentCardsStore = defineStore({ 39 | id: 'paymentCards', 40 | state: () => ({ 41 | paymentCards: [] as PaymentCard[], 42 | loading: false, 43 | }), 44 | getters: { 45 | currentPaymentCard: (state): PaymentCard | undefined => state.paymentCards.find((card) => card.isPrimary), 46 | allPaymentCards: (state) => state.paymentCards, 47 | }, 48 | actions: { 49 | async load() { 50 | this.loading = true 51 | this.paymentCards = await fetchPaymentCards() 52 | this.loading = false 53 | }, 54 | create(card: PaymentCard) { 55 | this.paymentCards.unshift(card) 56 | }, 57 | update(card: PaymentCard) { 58 | const index = this.paymentCards.findIndex((existingCard) => existingCard.id === card.id) 59 | if (index !== -1) { 60 | this.paymentCards.splice(index, 1, card) 61 | } 62 | }, 63 | remove(cardId: string) { 64 | this.paymentCards = this.paymentCards.filter((card) => card.id !== cardId) 65 | }, 66 | }, 67 | }) 68 | -------------------------------------------------------------------------------- /src/stores/projects.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { addProject, getProjects, Pagination, removeProject, Sorting, updateProject } from '../data/pages/projects' 3 | import { Project } from '../pages/projects/types' 4 | 5 | export const useProjectsStore = defineStore('projects', { 6 | state: () => { 7 | return { 8 | items: [] as Project[], 9 | pagination: { 10 | page: 1, 11 | perPage: 10, 12 | total: 0, 13 | } as Pagination, 14 | } 15 | }, 16 | 17 | actions: { 18 | async getAll(options: { pagination: Pagination; sorting?: Sorting }) { 19 | const { data, pagination } = await getProjects({ 20 | ...options.sorting, 21 | ...options.pagination, 22 | }) 23 | this.items = data 24 | this.pagination = pagination 25 | }, 26 | 27 | async add(project: Omit) { 28 | const [newProject] = await addProject(project) 29 | this.items.push(newProject) 30 | }, 31 | 32 | async update(project: Project) { 33 | const [updatedProject] = await updateProject(project) 34 | const index = this.items.findIndex(({ id }) => id === project.id) 35 | this.items.splice(index, 1, updatedProject) 36 | }, 37 | 38 | async remove(project: Project) { 39 | const isRemoved = await removeProject(project) 40 | 41 | if (isRemoved) { 42 | const index = this.items.findIndex(({ id }) => id === project.id) 43 | this.items.splice(index, 1) 44 | } 45 | }, 46 | }, 47 | }) 48 | -------------------------------------------------------------------------------- /src/stores/user-store.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | export const useUserStore = defineStore('user', { 4 | state: () => { 5 | return { 6 | userName: 'Vasili Savitski', 7 | email: 'vasili@gmail.com', 8 | memberSince: '8/12/2020', 9 | pfp: 'https://picsum.photos/id/22/200/300', 10 | is2FAEnabled: false, 11 | } 12 | }, 13 | 14 | actions: { 15 | toggle2FA() { 16 | this.is2FAEnabled = !this.is2FAEnabled 17 | }, 18 | 19 | changeUserName(userName: string) { 20 | this.userName = userName 21 | }, 22 | }, 23 | }) 24 | -------------------------------------------------------------------------------- /src/stores/users.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { 3 | addUser, 4 | type Filters, 5 | getUsers, 6 | Pagination, 7 | removeUser, 8 | Sorting, 9 | updateUser, 10 | uploadAvatar, 11 | } from '../data/pages/users' 12 | import { User } from '../pages/users/types' 13 | 14 | export const useUsersStore = defineStore('users', { 15 | state: () => { 16 | return { 17 | items: [] as User[], 18 | pagination: { page: 1, perPage: 10, total: 0 }, 19 | } 20 | }, 21 | 22 | actions: { 23 | async getAll(options: { pagination?: Pagination; sorting?: Sorting; filters?: Partial }) { 24 | const { data, pagination } = await getUsers({ 25 | ...options.filters, 26 | ...options.sorting, 27 | ...options.pagination, 28 | }) 29 | this.items = data 30 | this.pagination = pagination 31 | }, 32 | 33 | async add(user: User) { 34 | const [newUser] = await addUser(user) 35 | this.items.unshift(newUser) 36 | return newUser 37 | }, 38 | 39 | async update(user: User) { 40 | const [updatedUser] = await updateUser(user) 41 | const index = this.items.findIndex(({ id }) => id === user.id) 42 | this.items.splice(index, 1, updatedUser) 43 | return updatedUser 44 | }, 45 | 46 | async remove(user: User) { 47 | const isRemoved = await removeUser(user) 48 | 49 | if (isRemoved) { 50 | const index = this.items.findIndex(({ id }) => id === user.id) 51 | this.items.splice(index, 1) 52 | } 53 | }, 54 | 55 | async uploadAvatar(formData: FormData) { 56 | return uploadAvatar(formData) 57 | }, 58 | }, 59 | }) 60 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | const textSizes = { 2 | regularSmall: { 3 | fontSize: '0.8125rem', 4 | lineHeight: '1rem', 5 | }, 6 | } 7 | 8 | module.exports = { 9 | content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], 10 | theme: { 11 | extend: { 12 | fontSize: { 13 | tag: ['0.5625rem', '0.875rem'], 14 | regularSmall: ['0.8125rem', '1rem'], 15 | regularLarge: ['1.125rem', '1.625rem'], 16 | regularMedium: ['0.875rem', '1.25rem'], 17 | }, 18 | maxWidth: { 19 | '7xl': '1128px', 20 | }, 21 | colors: { 22 | primary: 'var(--va-primary)', 23 | secondary: 'var(--va-secondary)', 24 | success: 'var(--va-success)', 25 | info: 'var(--va-info)', 26 | danger: 'var(--va-danger)', 27 | warning: 'var(--va-warning)', 28 | backgroundPrimary: 'var(--va-background-primary)', 29 | backgroundSecondary: 'var(--va-background-secondary)', 30 | backgroundElement: 'var(--va-background-element)', 31 | backgroundCardPrimary: 'var(--va-background-card-primary)', 32 | backgroundCardSecondary: 'var(--va-background-card-secondary)', 33 | backgroundBorder: 'var(--va-background-border)', 34 | textPrimary: 'var(--va-text-primary)', 35 | textInverted: 'var(--va-text-inverted)', 36 | shadow: 'var(--va-shadow)', 37 | focus: 'var(--va-focus)', 38 | }, 39 | screens: { 40 | xs: '0px', 41 | sm: '640px', 42 | md: '1024px', 43 | lg: '1440px', 44 | xl: '1920px', 45 | }, 46 | }, 47 | screens: { 48 | xs: '0px', 49 | sm: '576px', 50 | md: '768px', 51 | lg: '992px', 52 | xl: '1200px', 53 | }, 54 | }, 55 | plugins: [], 56 | } 57 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "target": "esnext", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "moduleResolution": "bundler", 8 | "strict": true, 9 | "jsx": "preserve", 10 | "sourceMap": true, 11 | "resolveJsonModule": true, 12 | "esModuleInterop": true, 13 | "lib": ["esnext", "dom"], 14 | "skipLibCheck": true, 15 | "types": ["vite/client"], 16 | "allowJs": true 17 | }, 18 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue", "vite.config.ts", "node_modules/vuestic-ui"], 19 | "exclude": ["node_modules/**/*"] 20 | } 21 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import { resolve, dirname } from 'node:path' 4 | import { fileURLToPath } from 'url' 5 | import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite' 6 | import { vuestic } from '@vuestic/compiler/vite' 7 | 8 | // https://vitejs.dev/config/ 9 | export default defineConfig({ 10 | build: { 11 | sourcemap: true, 12 | }, 13 | plugins: [ 14 | vuestic({ 15 | devtools: true, 16 | cssLayers: true, 17 | }), 18 | vue(), 19 | VueI18nPlugin({ 20 | include: resolve(dirname(fileURLToPath(import.meta.url)), './src/i18n/locales/**'), 21 | }), 22 | ], 23 | }) 24 | --------------------------------------------------------------------------------