├── .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 |
2 |
3 |
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 |
2 |
3 |
9 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
24 |
91 |
92 |
97 |
--------------------------------------------------------------------------------
/src/components/icons/VaIconCleanCode.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | overview_icon_4
5 |
6 |
7 |
8 |
12 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
41 |
--------------------------------------------------------------------------------
/src/components/icons/VaIconColor.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
31 |
32 |
39 |
--------------------------------------------------------------------------------
/src/components/icons/VaIconDiscord.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/components/icons/VaIconFaster.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 62EBC3B8-A55C-4B01-95A2-52FB8EDD4150
5 |
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
28 |
--------------------------------------------------------------------------------
/src/components/icons/VaIconFree.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | overview_icon_2
5 |
6 |
7 |
11 |
12 |
16 |
20 |
21 |
22 |
23 |
24 |
25 |
47 |
--------------------------------------------------------------------------------
/src/components/icons/VaIconFresh.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | overview_icon_5
5 |
6 |
7 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
35 |
--------------------------------------------------------------------------------
/src/components/icons/VaIconGitHub.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/components/icons/VaIconHideSidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
15 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/components/icons/VaIconMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
23 |
24 |
31 |
--------------------------------------------------------------------------------
/src/components/icons/VaIconMenuCollapsed.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
25 |
26 |
33 |
--------------------------------------------------------------------------------
/src/components/icons/VaIconMessage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
20 |
21 |
28 |
--------------------------------------------------------------------------------
/src/components/icons/VaIconNotification.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
20 |
--------------------------------------------------------------------------------
/src/components/icons/VaIconResponsive.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | overview_icon_3
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
30 |
--------------------------------------------------------------------------------
/src/components/icons/VaIconRich.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | overview_icon_6
5 |
6 |
7 |
8 |
12 |
13 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
41 |
--------------------------------------------------------------------------------
/src/components/icons/VaIconSlower.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 67046716-A590-445C-AC65-1EEF69089C00
5 |
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
28 |
--------------------------------------------------------------------------------
/src/components/icons/VaIconVue.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | overview_icon_1
5 |
6 |
7 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
35 |
--------------------------------------------------------------------------------
/src/components/navbar/AppNavbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
39 |
40 |
80 |
--------------------------------------------------------------------------------
/src/components/navbar/components/AppNavbarActions.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 | support_agent
12 | {{ t('supportAndConsulting') }}
13 |
14 |
22 | info
23 | {{ t('aboutVuesticAdmin') }}
24 |
25 |
26 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
54 |
55 |
98 |
--------------------------------------------------------------------------------
/src/components/navbar/components/GitHubButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
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 |
2 |
3 |
4 |
5 |
27 |
28 |
48 |
--------------------------------------------------------------------------------
/src/components/va-charts/chart-types/BarChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
17 |
--------------------------------------------------------------------------------
/src/components/va-charts/chart-types/BubbleChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
--------------------------------------------------------------------------------
/src/components/va-charts/chart-types/DoughnutChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
--------------------------------------------------------------------------------
/src/components/va-charts/chart-types/HorizontalBarChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
27 |
--------------------------------------------------------------------------------
/src/components/va-charts/chart-types/LineChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
73 |
--------------------------------------------------------------------------------
/src/components/va-charts/chart-types/Map.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
67 |
--------------------------------------------------------------------------------
/src/components/va-charts/chart-types/PieChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | {{ $props.date }}
16 |
17 |
18 |
19 |
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 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
84 |
85 |
92 |
--------------------------------------------------------------------------------
/src/layouts/AuthLayout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
42 |
--------------------------------------------------------------------------------
/src/layouts/RouterBypass.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
Page not found
15 |
16 |
17 | The page you are looking for might have been removed had its name changed or is temporarily unavailable.
18 |
19 |
20 |
21 | Go to homepage
22 | Create a GitHub issue
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/pages/admin/dashboard/Dashboard.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 | Dashboard
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/pages/admin/dashboard/DataSection.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
81 |
--------------------------------------------------------------------------------
/src/pages/admin/dashboard/DataSectionItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ value }}
7 |
14 |
15 |
16 |
17 |
18 |
{{ title }}
19 |
20 |
21 | ↑
22 | ↓
23 | {{ changeText }}
24 |
25 | since last month
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
51 |
--------------------------------------------------------------------------------
/src/pages/admin/dashboard/cards/MonthlyEarnings.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Monthly Earnings
5 |
6 |
7 |
8 |
9 |
10 |
11 | $6,820
12 |
13 |
14 | 25.36%
15 | last month
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
66 |
--------------------------------------------------------------------------------
/src/pages/admin/dashboard/cards/ProjectTable.vue:
--------------------------------------------------------------------------------
1 |
23 |
24 |
25 |
26 |
27 | Projects
28 | View all projects
29 |
30 |
31 |
32 |
39 |
40 |
41 | {{ rowData.project_name }}
42 |
43 |
44 |
45 |
46 |
51 | {{ getUserById(rowData.project_owner)?.fullname }}
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | No projects
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/pages/admin/dashboard/cards/RegionRevenue.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Revenue by Top Regions
5 |
6 |
7 |
8 |
9 |
10 | Export
11 |
12 |
13 |
21 | ${{ rowData[`revenue${selectedPeriod}`] }}
22 |
23 |
24 |
25 |
26 |
27 |
83 |
84 |
93 |
--------------------------------------------------------------------------------
/src/pages/admin/dashboard/cards/RevenueByLocationMap.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Revenue by location
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
59 |
60 |
66 |
--------------------------------------------------------------------------------
/src/pages/admin/dashboard/cards/RevenueReport.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Revenue Report
5 |
6 |
7 | Export
8 |
9 |
10 |
11 |
12 |
13 |
{{ formatMoney(totalEarnings) }}
14 |
Total earnings
15 |
16 |
17 |
18 |
19 |
20 | Earnings this month
21 |
22 |
{{ formatMoney(earningsForSelectedMonth.earning) }}
23 |
24 |
25 |
26 |
27 | Expense this month
28 |
29 |
{{ formatMoney(earningsForSelectedMonth.expenses) }}
30 |
31 |
32 |
33 |
38 |
39 |
40 |
41 |
42 |
72 |
--------------------------------------------------------------------------------
/src/pages/admin/dashboard/cards/RevenueReportChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
104 |
105 |
112 |
--------------------------------------------------------------------------------
/src/pages/admin/dashboard/cards/Timeline.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | Timeline
9 |
10 |
11 |
12 |
13 |
14 | Donald updated the status of
15 | Refund #1234 to awaiting customer
16 | response
17 |
18 |
19 | Lycy Peterson was added to the group,
20 | group name is Overtake
21 |
22 |
23 | Joseph Rust opened new showcase
24 | Mannat #112233 with theme market
25 |
26 |
27 | Donald updated the status to awaiting
28 | customer response
29 |
30 |
31 | Lycy Peterson was added to the group
32 |
33 |
34 | Dan Rya was added to the group
35 |
36 |
37 | Project Vuestic 2023 was created
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/pages/admin/dashboard/cards/YearlyBreakup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Yearly Breakup
5 |
6 |
7 |
8 | $36,358
9 |
10 |
11 | +2,5%
12 | last year
13 |
14 |
15 |
16 |
17 | Earnings
18 |
19 |
20 |
21 | Profit
22 |
23 |
24 |
25 |
26 |
33 |
34 |
35 |
36 |
37 |
38 |
63 |
--------------------------------------------------------------------------------
/src/pages/admin/pages/404PagesPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 | {{ item.label }}
11 |
12 |
13 | {{ 'View Example' }}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
47 |
--------------------------------------------------------------------------------
/src/pages/auth/CheckTheEmail.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Check the email
4 |
5 | Password reset instructions have been sent to your email. Check your inbox, including the spam folder if needed.
6 | For assistance, contact support .
7 |
8 |
9 |
10 | Back to login
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/pages/auth/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Log in
4 |
5 | New to Vuestic?
6 | Sign up
7 |
8 |
15 |
16 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Forgot password?
38 |
39 |
40 |
41 |
42 | Login
43 |
44 |
45 |
46 |
47 |
70 |
--------------------------------------------------------------------------------
/src/pages/auth/RecoverPassword.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Forgot your password?
4 |
5 | If you've forgotten your password, don't worry. Simply enter your email address below, and we'll send you an email
6 | with a temporary password. Restoring access to your account has never been easier.
7 |
8 |
15 | Send password
16 | Go back
17 |
18 |
19 |
20 |
35 |
--------------------------------------------------------------------------------
/src/pages/billing/BillingPage.vue:
--------------------------------------------------------------------------------
1 |
2 | Billing information
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
26 |
--------------------------------------------------------------------------------
/src/pages/billing/Invoices.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Invoices
5 |
6 |
7 |
8 | {{ item.date }}
9 |
10 |
11 | {{ item.amount }}
12 |
13 |
14 | Download
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Show more
24 |
25 | Show less
26 |
27 |
28 |
29 |
30 |
95 |
--------------------------------------------------------------------------------
/src/pages/billing/modals/ChangeYourPaymentPlan.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 | Are you sure you want to switch to the
14 | {{ yearlyPlan ? 'monthly' : 'annual' }}
15 | plan?
16 |
17 |
18 | Cancel
19 | Update Plan
20 |
21 |
22 |
23 |
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 |
2 | How can we help you?
3 |
4 |
5 |
6 |
7 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{ category.name }}
13 | {{ category.intro }}
14 |
15 |
16 |
17 |
18 |
19 | No matches found. Try refining your search or browse through the categories to find the help you need.
20 |
21 |
22 |
23 |
39 |
--------------------------------------------------------------------------------
/src/pages/faq/widgets/Navigation.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{ section }}
6 |
11 |
12 |
13 |
14 |
15 |
16 |
24 |
--------------------------------------------------------------------------------
/src/pages/faq/widgets/RequestDemo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Got questions?
6 | Request a free demo to have all your questions answered by an expert.
7 |
8 |
9 | Request a demo
10 |
11 |
12 |
13 |
14 |
15 |
16 | Request free demo
17 |
18 | Claim your spot now and ignite innovation with our exceptional software solution! 🔥
19 |
20 |
27 |
28 |
29 |
30 |
31 |
53 |
--------------------------------------------------------------------------------
/src/pages/payments/PaymentsPage.vue:
--------------------------------------------------------------------------------
1 |
2 | Payment methods
3 |
4 |
5 |
6 | My cards
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Billing address
15 |
16 |
17 |
18 |
19 |
Tax location
20 |
21 |
United States - 10% VAT
22 |
More info
23 |
24 |
25 |
26 |
27 |
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 |
2 |
3 |
4 |
5 |
6 |
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 |
2 |
3 | Add Billing Address
4 |
10 |
11 |
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 |
2 |
3 |
9 |
10 |
16 |
22 |
28 |
34 |
40 |
41 | Cancel
42 | {{ submitText }}
43 |
44 |
45 |
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 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
26 |
30 |
31 |
Important note
32 |
33 | Please ensure the provided billing address matches the information on file with your financial institution
34 | to avoid any processing delays.
35 |
36 |
37 |
New address
38 |
39 |
40 |
41 |
42 |
43 |
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 |
2 |
5 |
6 |
7 |
{{ billingAddress.name }}
8 |
9 |
10 |
11 |
{{ billingAddress.street }}
12 |
{{ billingAddress.city }}, {{ billingAddress.state }} {{ billingAddress.postalCode }}
13 |
{{ billingAddress.country }}
14 |
15 |
16 |
17 | Edit
18 |
19 |
20 |
21 |
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 |
2 |
3 | Add Billing Address
4 |
10 |
11 |
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 |
2 |
3 | Add payment card
4 |
5 |
6 |
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 |
2 |
3 |
9 |
10 |
17 |
25 |
38 |
39 |
40 | Cancel
41 | {{ submitText }}
42 |
43 |
44 |
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 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
26 |
30 |
31 |
Important note
32 |
33 | Please carefully read Product Terms before adding your new payment card
34 |
35 |
36 |
Add card
37 |
38 |
39 |
40 |
41 |
42 |
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 |
2 |
5 |
6 |
7 |
{{ card.name }}
8 |
9 |
10 |
11 |
12 |
13 |
{{ card.paymentSystem }} {{ card.cardNumberMasked }}
14 |
Card expires at {{ expirationDateString }}
15 |
16 |
17 |
18 |
19 | Edit
20 |
21 |
22 |
23 |
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 |
2 |
3 | Add payment card
4 |
5 |
6 |
7 |
8 |
33 |
--------------------------------------------------------------------------------
/src/pages/preferences/Preferences.vue:
--------------------------------------------------------------------------------
1 |
2 | Preferences
3 |
11 |
12 |
13 |
14 |
25 |
--------------------------------------------------------------------------------
/src/pages/preferences/modals/EditNameModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 | Reset password
12 |
13 |
14 |
15 | Cancel
16 | Save
17 |
18 |
19 |
20 |
21 |
46 |
47 |
53 |
--------------------------------------------------------------------------------
/src/pages/preferences/preferences-header/PreferencesHeader.vue:
--------------------------------------------------------------------------------
1 |
2 | 😍
3 |
4 |
{{ store.userName }}
5 |
6 |
Member since
7 |
{{ store.memberSince }}
8 |
9 |
10 |
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 |
21 |
22 |
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 |
26 |
31 |
37 |
38 | {{ new Date(project.created_at).toLocaleDateString() }}
39 |
40 |
41 | {{ project.project_name }}
42 |
43 |
44 | Owner:
45 | {{ getUserById(project.project_owner)!.fullname }}
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | No projects
59 |
60 |
--------------------------------------------------------------------------------
/src/pages/settings/Settings.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Settings
4 |
5 |
Theme
6 |
7 |
8 |
9 |
General preferences
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
Your notification settings are regrouped and simplified
18 |
Your previous setting choices aren't changed.
19 |
20 |
21 |
22 |
23 |
24 |
29 |
--------------------------------------------------------------------------------
/src/pages/settings/language-switcher/LanguageSwitcher.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Language
4 |
5 |
6 |
7 |
8 |
9 |
47 |
--------------------------------------------------------------------------------
/src/pages/settings/notifications/Notifications.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Notifications you receive
4 |
5 |
6 |
7 | {{ notification.name }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
--------------------------------------------------------------------------------
/src/pages/settings/theme-switcher/ThemeSwitcher.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
39 |
45 |
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 |
--------------------------------------------------------------------------------