Showing preview only (1,879K chars total). Download the full file or copy to clipboard to get everything.
Repository: epicmaxco/vuestic-admin
Branch: master
Commit: 9c5b44f3674d
Files: 220
Total size: 1.8 MB
Directory structure:
gitextract_vqblgh86/
├── .editorconfig
├── .eslintrc.cjs
├── .gitattributes
├── .github/
│ ├── COMMIT_CONVENTION.md
│ ├── CONTRIBUTING.md
│ ├── ISSUE_TEMPLATE.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── dependabot.yml
│ └── workflows/
│ └── playwright.yml
├── .gitignore
├── .husky/
│ ├── .gitignore
│ └── pre-commit
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .storybook/
│ ├── main.ts
│ ├── preview.ts
│ └── storybook-main.scss
├── .vscode/
│ └── extensions.json
├── .yarnrc.yml
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.ja-JP.md
├── README.md
├── README.zh-CN.md
├── _redirects
├── docs/
│ └── pre-production.md
├── e2e/
│ ├── .gitignore
│ ├── README.MD
│ ├── package.json
│ ├── playwright.config.ts
│ ├── stubs/
│ │ ├── index.ts
│ │ ├── projects.ts
│ │ └── users.ts
│ ├── tests/
│ │ └── vuestic-admin.spec.ts
│ └── utils/
│ └── index.ts
├── index.html
├── netlify.toml
├── package.json
├── postcss.config.js
├── public/
│ └── site.webmanifest
├── 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
│ │ │ └── widgets/
│ │ │ ├── Categories.vue
│ │ │ ├── Navigation.vue
│ │ │ ├── Questions.vue
│ │ │ └── RequestDemo.vue
│ │ ├── payments/
│ │ │ ├── PaymentsPage.vue
│ │ │ ├── payment-system/
│ │ │ │ ├── PaymentSystem.stories.ts
│ │ │ │ └── PaymentSystem.vue
│ │ │ ├── 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.scss
│ │ ├── 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
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
================================================
FILE: .eslintrc.cjs
================================================
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
'vue/setup-compiler-macros': true,
},
plugins: ['@typescript-eslint'],
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
sourceType: 'module',
ecmaVersion: 2021,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:vue/vue3-recommended',
'@vue/typescript/recommended',
'@vue/prettier',
'plugin:storybook/recommended',
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'@typescript-eslint/no-non-null-assertion': 0,
'@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
'vue/multi-word-component-names': 0,
'vue/no-lone-template': 0,
'vue/v-on-event-hyphenation': ['warn', 'never', { autofix: true }],
'vue/component-name-in-template-casing': ['warn', 'PascalCase', { registeredComponentsOnly: false }],
'vue/script-indent': ['warn', 2], // , { baseIndent: 0 } - we should use that, but it didn't work for me for some reason.
},
}
================================================
FILE: .gitattributes
================================================
e2e/__screenshots__/** filter=lfs diff=lfs merge=lfs -text
================================================
FILE: .github/COMMIT_CONVENTION.md
================================================
## Git Commit Message Convention
> This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular).
#### TL;DR:
Messages must match the following regex:
```js
;/^(revert: )?(feat|fix|docs|style|refactor|test|ci|chore)(\(.+\))?: .{1,70}/
```
#### Types
- `feat` - new functionality.
- `fix` - patching a bug.
- `docs` - documentation and comments.
- `style` - style changes only (not necessarily in css).
- `refactor` - reworking internals without impacting project interface.
- `test` - tests and demo.
- `ci` - deploy and continuous integration.
- `chore` - no significant code changes: code formatting, version changes, tool updates, minor refactorings.
#### Examples
Appears under "Features" header, `compiler` subheader:
```
feat(compiler): add 'comments' option
```
Appears under "Bug Fixes" header, `v-model` subheader, with a link to issue #28:
```
fix(v-model): handle events on blur
close #28
```
Appears under "Performance Improvements" header, and under "Breaking Changes" with the breaking change explanation:
```
feat(core): improve vdom diffing by removing 'foo' option
BREAKING CHANGE: The 'foo' option has been removed.
```
The following commit and commit `667ecc1` do not appear in the changelog if they are under the same release. If not, the revert commit appears under the "Reverts" header.
```
revert: feat(compiler): add 'comments' option
This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
```
### Full Message Format
A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:
```
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```
The **header** is mandatory and the **scope** of the header is optional.
### Revert
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
### Type
If the prefix is `feat` or `fix` it will appear in the changelog. However if there is any [BREAKING CHANGE](#footer), the commit will always appear in the changelog.
Other prefixes are up to your discretion. Suggested prefixes are `docs`, `chore`, `style`, `refactor`, and `test` for non-changelog related tasks.
### Scope
The scope could be anything specifying place of the commit change. For example `core`, `compiler`, `ssr`, `v-model`, `transition` etc...
### Subject
The subject contains succinct description of the change:
- use the imperative, present tense: "change" not "changed" nor "changes"
- don't capitalize first letter
- no period (.) at the end
### Body
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
The body should include the motivation for the change and contrast this with previous behavior.
### Footer
The footer should contain any information about **Breaking Changes** and is also the place to
reference GitHub issues that this commit **Closes**.
**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Vue.js Contributing Guide
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.
- [Code of Conduct](./../CODE_OF_CONDUCT.md)
## Pull Request Guidelines
- The `master` branch is just a snapshot of the latest stable release. **Do not submit PRs against the `master` branch.**
- Atomic code contribution looks something like this:
- Checkout from upstream `develop`.
- Work on your fork in dedicated branch.
- 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 ;).
- It's OK to have multiple small commits as you work on the PR - we will let GitHub automatically squash it before merging.
- Good stuff to add in your pull request:
- 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 :).
- Provide detailed description of the issue in the PR if it's not done in the issue.
- If you're working on visual changes - provide before/after screenshot. That speeds up review immensely.
### Branches
- Upstream branches (**epicmax/vuestic-admin**):
- `master` - stable snapshot from `develop`. Releases and hotfixes only. Do not submit PR's to `master`!.
- `develop` - main development branch.
- Local branches
- For local branches naming stick to [commit message convention](./COMMIT_CONVENTION.md). So for feature branch that adds tabs name would be `feat/tabs`.
### For core contributors
- Keep amount of local branches minimal.
- Always link PR to issue (via `fix #123`).
- For small issues you may push to `develop` branch directly while adding (`fix #123`) to commit message.
- 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.
- Be sure to have only one person assigned per issue.
- Check your code: https://github.com/epicmaxco/vuestic-admin/issues/378.
- We use [yarn](https://yarnpkg.com/lang/en/) for package management.
- Be proactive. If you think something is wrong - create an issue or discuss.
- Recommended tools: [GitKraken](https://www.gitkraken.com/), [WebStorm](https://www.jetbrains.com/webstorm/), [ShareX](https://getsharex.com/)
#### Before release workflow
- Update package versions to newest ones. Update lock files (for both `npm` and `yarn`)
### Vuestic-ui
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).
### Commonly used NPM scripts
```bash
# run dev server
$ yarn dev
# build vuestic-admin project into bundle
$ yarn build
```
## Credits
<a href="https://github.com/epicmaxco/vuestic-admin/graphs/contributors">Hall of fame!</a>
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
**Note: for support questions, please use stackoverflow**. This repository's issues are reserved for feature requests and bug reports.
- **I'm submitting a ...**
- [ ] bug report
- [ ] feature request
- [ ] support request => Please do not submit support request here, see note at the top of this template.
- **Do you want to request a _feature_ or report a _bug_?**
- **What is the current behavior?**
- **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**
- **What is the expected behavior?**
- **What is the motivation / use case for changing the behavior?**
- **Other information** (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. stackoverflow, gitter, etc)
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--
MAKE SURE TO READ THE CONTRIBUTING GUIDE BEFORE CREATING A PR
https://github.com/epicmaxco/vuestic-admin/blob/master/CODE_OF_CONDUCT.md
-->
<!-- Provide a general summary of your changes in the Title above -->
<!-- Keep the title short and descriptive, as it will be used as a commit message -->
## Description
<!-- Describe your changes in detail -->
## Markup:
<!-- Paste your markup here. -->
<details>
```vue
// Your code
```
</details>
## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Improvement/refactoring (non-breaking change that doesn't add any feature but make things better)
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
target-branch: "develop"
================================================
FILE: .github/workflows/playwright.yml
================================================
name: Playwright Tests
on:
workflow_dispatch:
inputs:
target_url:
description: 'Netlify build URL to test (e.g. https://your-preview.netlify.app)'
required: true
type: string
baseline_url:
description: 'Baseline URL for snapshot generation (optional)'
required: false
default: https://admin-demo.vuestic.dev
type: string
publish_report:
description: 'Publish Playwright report to Netlify (new deploy per run)'
required: false
type: boolean
default: false
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
concurrency:
group: playwright-${{ inputs.target_url }}
cancel-in-progress: false
steps:
- name: Checkout selected ref (pinned to master)
uses: actions/checkout@v4
with:
ref: master
fetch-depth: 1
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Enable Corepack and select Yarn from package.json
run: |
corepack enable
corepack use $(node -p "require('./package.json').packageManager")
yarn --version
- name: Cache Yarn cache
uses: actions/cache@v4
with:
path: .yarn/cache
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install --immutable
- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-pw-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-pw-
- name: Install Playwright Browsers
run: yarn workspace e2e exec playwright install --with-deps
- name: Generate baseline snapshots
run: yarn e2e --update-snapshots
env:
E2E_BASE_URL: ${{ inputs.baseline_url }}
- name: Run Playwright tests (against Netlify build)
run: yarn e2e
env:
E2E_BASE_URL: ${{ inputs.target_url }}
continue-on-error: true #! Temporary: ignore screenshot diffs in CI to avoid blocking PRs.
# TODO: remove once visual tests are stabilized.
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report-${{ github.sha }}
path: e2e/playwright-report/**
- name: Publish Playwright report to Netlify
if: ${{ always() && inputs.publish_report == true }}
uses: nwtgck/actions-netlify@v3.0
with:
publish-dir: 'e2e/playwright-report'
production-deploy: false
alias: run-${{ github.run_id }}
deploy-message: "Playwright report for ${{ github.sha }} (target: ${{ inputs.target_url }})"
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_REPORT_SITE_ID }}
================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
.yarn/install-state.gz
node_modules
dist
dist-ssr
*.local
.env
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Local Netlify folder
.netlify
================================================
FILE: .husky/.gitignore
================================================
_
================================================
FILE: .husky/pre-commit
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
================================================
FILE: .nvmrc
================================================
20
================================================
FILE: .prettierignore
================================================
# Ignore everything recursively...
*
# But not the .{ts,js,html,css,scss,vue,json,md} files
!*.ts
!*.js
!*.html
!*.css
!*.scss
!*.vue
!*.json
!*.md
# But still ignore the dist folder
dist/**
# Check subdirectories too
!*/
================================================
FILE: .prettierrc
================================================
{
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"quoteProps": "as-needed",
"trailingComma": "all",
"bracketSpacing": true,
"printWidth": 120
}
================================================
FILE: .storybook/main.ts
================================================
import type { StorybookConfig } from '@storybook/vue3-vite'
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'],
framework: {
name: '@storybook/vue3-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
}
export default config
================================================
FILE: .storybook/preview.ts
================================================
import { type Preview, setup } from '@storybook/vue3'
import { createVuestic } from 'vuestic-ui'
import vuesticGlobalConfig from '../src/services/vuestic-ui/global-config'
import './storybook-main.scss'
import '../src/scss/main.scss'
import { createPinia } from 'pinia'
const pinia = createPinia()
setup((app) => {
app.use(createVuestic({ config: vuesticGlobalConfig }))
app.use(pinia)
})
const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
}
export default preview
================================================
FILE: .storybook/storybook-main.scss
================================================
@import url('https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.min.css');
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
@import url(https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined);
================================================
FILE: .vscode/extensions.json
================================================
{
"recommendations": ["Vue.volar"]
}
================================================
FILE: .yarnrc.yml
================================================
nodeLinker: node-modules
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2024 Epicmax LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.ja-JP.md
================================================
<p align="center">
<a href="https://vuestic.dev" target="_blank">
<img alt="Vuestic UI ロゴ" width="220" src="./.github/assets/vuestic-admin-logo.png">
</a>
</p>
<p align="center">
Vue 3、Vite、Pinia、およびTailwind CSS を利用した無料で美しい管理テンプレート。効率的でレスポンシブ、かつ高速な管理インターフェースの構築に最適です。</br>
開発者: <a href="https://epicmax.co">Epicmax</a>。</br>
<a href="https://ui.vuestic.dev">Vuestic UI</a> ライブラリをベースにしています。
</p>
<p align="center">
<a href="https://admin-demo.vuestic.dev"> ライブデモ </a> |
<a href="https://admin-landing.vuestic.dev/"> Vuestic Admin について </a> |
<a href="https://ui.vuestic.dev/">Vuestic UI ドキュメンテーション</a>
</p>
> Vuestic Admin は [Vuestic UI](https://ui.vuestic.dev) で構築されています。私たちの
> <a href="https://github.com/epicmaxco/vuestic-ui/issues">課題</a>、
> <a href="https://ui.vuestic.dev/en/contribution/guide">貢献ガイド</a> を参照して、
> <a href="https://discord.gg/jTKTjj2weV">Discord サーバー</a> でディスカッションに参加して、Vuestic Admin & Vuestic UI の体験を向上させるのに役立ててください。
<p align="center">
<a href="https://admin.vuestic.dev" target="_blank">
<img src="./public/vuestic-admin-image.png" align="center" width="888px"/>
</a>
</p>
### クイックスタート
次のコマンドを使用して、[Vuestic Admin](admin-demo.vuestic.ui) または空の Vite または Nuxt プロジェクトを [Vuestic UI](ui.vuestic.dev) と共に素早く構築します。
```bash
npm create vuestic@latest
```
[Vuestic Admin](admin.vuestic.ui) をインストールしたら、`npm install` を実行して依存関係をインストールし、次に `npm run dev` を実行してローカル開発サーバーを起動します。
### ドキュメンテーション
ドキュメンテーション、ガイド、例、およびチュートリアルは [ui.vuestic.dev](https://ui.vuestic.dev) で利用可能です。
### 公式 Discord サーバー
公式コミュニティの [Discord サーバー](https://discord.gg/jTKTjj2weV) で質問してください。
### 特徴
- **Vue 3、Vite、Pinia、および Tailwind CSS -** 高速かつ効率的な開発
- **ダークテーマ -** モダンで目を引く
- **グローバル構成 -** 無駄なくカスタマイズ可能
- **アクセシビリティ -** 包括的でユーザーフレンドリー
- **i18n 統合 -** グローバルな展開のための簡単なローカリゼーション
- **教育リソース -** 学習とスキル向上に最適
- **レスポンシブデザイン -** すべてのデバイスにシームレスに適応
- **プロフェッショナルサポート -** 専門家からの信頼性のあるサポート
- **高度にカスタマイズ可能 -** プロジェクトのスタイルに合わせて調整
### 貢献
素晴らしい PR、課題、アイデアに感謝します。
<a href="https://github.com/epicmaxco/vuestic-admin/graphs/contributors">
<img src="https://opencollective.com/vuestic-admin/contributors.svg?width=890&button=false" />
</a>
<br>
いつでも参加歓迎です:私たちの
<a href="https://ui.vuestic.dev/en/contribution/guide">
貢献ガイド</a>
、 [オープン課題](https://github.com/epicmaxco/vuestic-ui/issues)
および [Discord サーバー](https://discord.gg/jTKTjj2weV) を確認してください。
### パートナー & スポンサー ❤️
<img src="./.github/assets/sponsors.png" loading="lazy" alt="Epicmax、vuejobs、ag-grid、flatlogic、browserstack、jetbrains" width="400px">
パートナーになる: [hello@epicmax.co](mailto:hello@epicmax.co)
### お仕事の依頼はできますか?
[Epicmax](https://epicmax.co) は初めからオープンソースにコミットしています。Vuestic Admin は Epicmax によって作成され、そしてこれまでのすべての年月を通じてサポートされています。
6年以上にわたる商業およびオープンソースプロジェクトへの専念した作業、および世界中のさまざまな分野で47以上のクライアントを持つことで、Epicmaxは特にVue.jsにおけるフロントエンド開発の深い専門知識を有しています。私たちはプロジェクトのコード監査を定期的に実施しており、これまでのクライアントだけでなく、フロントエンドコードの状態を理解し、セキュアで最新であることを確認したいすべての方にこのサービスを提供することに興奮しています!
Epicmaxによるウェブ開発サービスの相談や注文は、この[フォーム](https://epicmax.co/contacts)からリクエストできます 😎
こんにちはと言いたい方は: [hello@epicmax.co](mailto:hello@epicmax.co)。一緒に仕事ができることを嬉しく思います!
[これまでの仕事](https://epicmax.co) 🤘
[チームに会う](https://ui.vuestic.dev/introduction/team)
### 受賞歴
[<img src="https://i.imgur.com/ZeQPZ3Q.png" align="center" width="150px"/>](https://flatlogic.com/templates/vuestic-vue-free-admin)
<p>
[Flatlogic](https://flatlogic.com/templates/vuestic-vue-free-admin) マーケットプレイスによる
</p>
### 私たちをフォローしてください
最新のVuesticニュースをお知らせします!
[Twitter](https://twitter.com/vuestic_ui) または [Linkedin](https://www.linkedin.com/company/18509340) でフォローしてください。
### ライセンス
[MIT](https://github.com/epicmaxco/vuestic-admin/blob/master/LICENSE) ライセンス。
================================================
FILE: README.md
================================================
<p align="center">
<a href="https://vuestic.dev" target="_blank">
<img alt="Vuestic UI Logo" width="220" src="./.github/assets/vuestic-admin-logo.png">
</a>
</p>
<p align="center">
Free and beautiful Admin Template utilizing Vue 3, Vite, Pinia, and Tailwind CSS. Designed for building efficient, responsive, and fast-loading admin interfaces.</br>
Developed by <a href="https://epicmax.co">Epicmax</a>.</br>
Based on <a href="https://ui.vuestic.dev">Vuestic UI</a> library.
</p>
<p align="center">
<a href="https://admin-demo.vuestic.dev"> Live Demo </a> |
<a href="https://admin.vuestic.dev/"> About Vuestic Admin </a> |
<a href="https://ui.vuestic.dev/">Vuestic UI documentation</a>
</p>
> Vuestic Admin is built with [Vuestic UI](https://ui.vuestic.dev). See our
> <a href="https://github.com/epicmaxco/vuestic-ui/issues">issues</a>,
> <a href="https://ui.vuestic.dev/en/contribution/guide">contributing guide</a> and join discussions on our
> <a href="https://discord.gg/jTKTjj2weV">Discord server</a> to help us improve Vuestic Admin & Vuestic UI experience.
<p align="center">
<a href="https://admin.vuestic.dev" target="_blank">
<img src="./public/vuestic-admin-image.png" align="center" width="888px"/>
</a>
</p>
### Quick start
Use following command to quickly scaffold new [Vuestic Admin](https://admin-demo.vuestic.dev) or empty Vite or Nuxt project with [Vuestic UI](https://ui.vuestic.dev).
```bash
npm create vuestic@latest
```
After [Vuestic Admin](https://admin.vuestic.dev) is installed, run `npm install` to install dependcies, then run `npm run dev` to start local development server.
### Documentation
Documentation, guides, examples and tutorials are available on [ui.vuestic.dev](https://ui.vuestic.dev)
### Official Discord Server
Ask questions at the official community [discord server](https://discord.gg/jTKTjj2weV)
### Features
- **Vue 3, Vite, Pinia, and Tailwind CSS -** Fast and efficient development
- **Dark Theme -** Modern and eye-catching
- **Global Configuration -** Effortless customization
- **Accessibility -** Inclusive and user-friendly
- **i18n Integration -** Easy localization for global reach
- **Educational Resource -** Ideal for learning and improving skills
- **Responsive Design -** Adapts seamlessly to all devices
- **Professional Support -** Reliable help from the experts
- **Highly Customizable -** Tailor to your project’s style
### Contributing
Thanks for all your wonderful PRs, issues and ideas.
<a href="https://github.com/epicmaxco/vuestic-admin/graphs/contributors">
<img src="https://opencollective.com/vuestic-admin/contributors.svg?width=890&button=false" />
</a>
<br>
You’re always welcome to join: check out
our <a href="https://ui.vuestic.dev/en/contribution/guide">
contribution guides</a>
, [open issues](https://github.com/epicmaxco/vuestic-ui/issues)
and [Discord server](https://discord.gg/jTKTjj2weV)
### Partners & Sponsors ❤️
<img src="./.github/assets/sponsors.png" loading="lazy" alt="Epicmax, vuejobs, ag-grid, flatlogic, browserstack and jetbrains" width="400px">
Become a partner: [hello@epicmax.co](mailto:hello@epicmax.co)
### Can I hire you guys?
[Epicmax](https://epicmax.co) is committed to Open Source from its beginning. Vuestic Admin was created and backed by Epicmax, and is supported through all the years.
With 6+ years of dedicated work on both commercial and open-source projects, and more than 47 clients worldwide across various fields, Epicmax has deep expertise in frontend development, especially in Vue.js. We regularly conduct code audits for our projects and now excited to offer this service not only to our existing clients but to anyone looking to understand the state of their frontend code and ensure it's secure and up-to-date!
You can request a consultation or order web development services by Epicmax via this [form](https://epicmax.co/contacts) 😎
Say hi: <a href="mailto:hello@epicmax.co">hello@epicmax.co</a>. We will be happy to work with you!
[Other work](https://epicmax.co) we’ve done 🤘
[Meet the Team](https://ui.vuestic.dev/introduction/team)
### Awards
<a href="https://flatlogic.com/templates/vuestic-vue-free-admin" target="_blank">
<img src="https://i.imgur.com/ZeQPZ3Q.png" align="center" width="150px"/>
</a>
<p>
By <a href="https://flatlogic.com/templates/vuestic-vue-free-admin" target="_blank">@flatlogic</a> marketplace
</p>
### Premium Support and Consulting
Get Premium Support & Consulting services through our official development partner, Epicmax. As the main contributor to Vuestic UI and Vuestic Admin, Epicmax brings a wealth of expertise and experience to help you achieve your project goals efficiently and effectively.
[Get a quote](https://www.epicmax.co/?ref=vuestic-consulting)
### Follow us
Stay up to date with the latest Vuestic news! Follow us
on [Twitter](https://twitter.com/vuestic_ui)
or [Linkedin](https://www.linkedin.com/company/18509340)
### License
[MIT](https://github.com/epicmaxco/vuestic-admin/blob/master/LICENSE) license.
================================================
FILE: README.zh-CN.md
================================================
<p align="center">
<a href="https://vuestic.dev" target="_blank">
<img alt="Vuestic UI Logo" width="220" src="./.github/assets/vuestic-admin-logo.png">
</a>
</p>
<p align="center">
免费且美观的管理模板,使用Vue 3、Vite、Pinia和Tailwind CSS构建。设计用于构建高效、响应式和快速加载的管理界面。</br>
由<a href="https://epicmax.co">Epicmax</a>开发。</br>
基于<a href="https://ui.vuestic.dev">Vuestic UI</a>库。
</p>
<p align="center">
<a href="https://admin-demo.vuestic.dev"> 在线演示 </a> |
<a href="https://admin-landing.vuestic.dev/"> 关于Vuestic Admin </a> |
<a href="https://ui.vuestic.dev/">Vuestic UI文档</a>
</p>
> Vuestic Admin是使用[Vuestic UI](https://ui.vuestic.dev)构建的。查看我们的
> <a href="https://github.com/epicmaxco/vuestic-ui/issues">问题</a>,
> <a href="https://ui.vuestic.dev/en/contribution/guide">贡献指南</a> 并参与我们的
> <a href="https://discord.gg/jTKTjj2weV">Discord服务器</a>,帮助我们改进Vuestic Admin和Vuestic UI体验。
<p align="center">
<a href="https://admin.vuestic.dev" target="_blank">
<img src="./public/vuestic-admin-image.png" align="center" width="888px"/>
</a>
</p>
### 快速入门
使用以下命令快速搭建新的[Vuestic Admin](admin-demo.vuestic.ui)或使用[Vuestic UI](ui.vuestic.dev)的空白Vite或Nuxt项目。
```bash
npm create vuestic@latest
```
安装[Vuestic Admin](admin.vuestic.ui)后,运行 `npm install` 安装依赖,然后运行 `npm run dev` 启动本地开发服务器。
### 文档
文档、指南、示例和教程可在[ui.vuestic.dev](https://ui.vuestic.dev)上找到。
### 官方Discord服务器
在官方社区的 [discord服务器](https://discord.gg/jTKTjj2weV)上提问。
### 特性
- **Vue 3、Vite、Pinia和Tailwind CSS -** 快速高效的开发
- **深色主题 -** 现代且引人注目
- **全局配置 -** 轻松定制
- **可访问性 -** 包容且用户友好
- **i18n集成 -** 便于全球本地化
- **教育资源 -** 适用于学习和提高技能
- **响应式设计 -** 无缝适应所有设备
- **专业支持 -** 专家提供可靠帮助
- **高度可定制 -** 可根据项目风格定制
### 贡献
感谢您所有出色的PR、问题和想法。
<a href="https://github.com/epicmaxco/vuestic-admin/graphs/contributors">
<img src="https://opencollective.com/vuestic-admin/contributors.svg?width=890&button=false" />
</a>
<br>
欢迎随时加入:查看我们的<a href="https://ui.vuestic.dev/en/contribution/guide">贡献指南</a>,[开放问题](https://github.com/epicmaxco/vuestic-ui/issues)和[Discord服务器](https://discord.gg/jTKTjj2weV)。
### 合作伙伴与赞助商 ❤️
<img src="./.github/assets/sponsors.png" loading="lazy" alt="Epicmax, vuejobs, ag-grid, flatlogic, browserstack and jetbrains" width="400px">
成为合作伙伴:[hello@epicmax.co](mailto:hello@epicmax.co)
### 我能雇佣你们吗?
[Epicmax](https://epicmax.co) 从一开始就致力于开源。Vuestic Admin是由Epicmax创建并支持的,经过多年的支持。
在前端开发方面,Epicmax在商业和开源项目上已经有超过6年的专业经验,与全球各个领域的47个以上的客户合作。我们定期对我们的项目进行代码审核,现在很高兴不仅向我们现有的客户提供此服务,而且向任何希望了解其前端代码状态并确保其安全和最新的人提供此服务!
您可以通过[Epicmax的这个表单](https://epicmax.co/contacts)请求咨询或订购Web开发服务 😎
打个招呼:<a href="mailto:hello@epicmax.co">hello@epicmax.co</a>。我们将很高兴与您合作!
[我们做过的其他工作](https://epicmax.co) 🤘
[认识团队](https://ui.vuestic.dev/introduction/team)
### 奖项
<a href="https://flatlogic.com/templates/vuestic-vue-free-admin" target="_blank">
<img src="https://i.imgur.com/ZeQPZ3Q.png" align="center" width="150px"/>
</a>
<p>
由<a href="https://flatlogic.com/templates/vuestic-vue-free-admin" target="_blank">@flatlogic</a>市场提供
</p>
### 关注我们
随时关注最新的Vuestic新闻!在[Twitter上](https://twitter.com/vuestic_ui)
或[Linkedin上](
================================================
FILE: _redirects
================================================
/* /index.html 200
================================================
FILE: docs/pre-production.md
================================================
# Pre-production
## SEO
We have a boierplate prepared with some analytics ingrained. This includes:
- [Yandex Metrica](https://metrica.yandex.com/about)
To get these running - just provide keys to respective apis. You're advised to copy `.env.example` with rename to `.env` then modify it.
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).
## Deploy
We use [circleci](https://circleci.com) to deploy vuestic version you're able to see on demo.
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**.
- `DEPLOY_PASSWORD` ssh password.
- `DEPLOY_PATH_PRODUCTION` production build will be loaded to this folder.
- `DEPLOY_PATH_STAGING` staging build will be loaded to this folder.
- `DEPLOY_URL` ssh url.
- `DEPLOY_USER` ssh password.
You can modify [config](../.circleci/config.yml) if our solution doesn't suit your needs exactly.
Couple of things to note:
- 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.
- Circleci will run tests before both staging and production.
## Demos
You can enable demos in build by:
```
VUE_APP_INCLUDE_DEMOS=true
```
Demos are included in staging build by default. They're not present in production because of significant impact on bundle size.
## Build Version
You can enable build version, hash commit, and timestamp by build to the main page footer:
```
VUE_APP_BUILD_VERSION=true
```
This information are excluded by default.
================================================
FILE: e2e/.gitignore
================================================
test-results
playwright-report
blob-report
playwright/.cache
__screenshots__
================================================
FILE: e2e/README.MD
================================================
# End-to-End Testing with Playwright
This project uses **Playwright** for end-to-end (E2E) testing.
All test logic lives in the `e2e` folder, but several root-level changes are required for correct setup.
---
## Project Setup
### 1. GitHub Actions (CI)
CI is configured in: `.github/workflows/playwright.yml`
The workflow is triggered manually.
You can provide:
- **Baseline URL** – used to generate reference snapshots (default: `https://admin-demo.vuestic.dev`).
- **Target URL** – Netlify (or other) build URL to run tests against (required).
The workflow runs tests and uploads the Playwright HTML report as a GitHub Artifact, which can be downloaded and inspected locally.
### 2. `package.json` changes
At the root level, add:
```json
{
"workspaces": ["e2e"],
"scripts": {
"e2e": "yarn workspace e2e test"
}
}
```
This ensures the E2E project is recognized as a Yarn workspace and provides a shortcut script for running tests.
---
## Configuration
All configuration is stored in: `e2e/playwright.config.ts`
Here you can customize:
- Paths – where snapshots, reports, and test results are stored.
- Snapshot Ratio – the allowed percent difference for visual comparisons.
- Timeouts – global test and assertion timeouts.
If you want to store baseline screenshots in the repository, we recommend adding a `.gitattributes` file to enforce consistent handling of binary files.
---
## API Stubs and Mocks
The `e2e` project includes stubs for API requests.
These allow you to intercept, control, and replace backend requests with mocks during testing.
---
## CI Workflow Details
- Generate/Update Baseline: snapshots are created against the baseline URL.
- Run Tests: tests are executed against the target URL (e.g., a Netlify preview build).
- Artifacts: Playwright reports are uploaded as GitHub Artifacts and can be downloaded for local review.
---
## Local Usage
Step 1:
// Expect error
Generate baseline screenshots: `E2E_BASE_URL=https://admin-demo.vuestic.dev yarn e2e:update-snapshots`
Step 2:
Build and run project locally: `yarn build:ci && yarn start:ci`
// Use correct API base url in .env
To run tests locally: `yarn e2e`
================================================
FILE: e2e/package.json
================================================
{
"name": "e2e",
"private": true,
"version": "0.0.0",
"scripts": {
"test": "playwright test",
"update": "playwright test --update-snapshots"
},
"devDependencies": {
"@playwright/test": "^1.54.2"
}
}
================================================
FILE: e2e/playwright.config.ts
================================================
import { defineConfig, devices } from '@playwright/test'
// Chromium – 3 breakpoints
const projectsChromium = [
{
name: 'chromium-pixel5',
use: {
...devices['Pixel 5'],
viewport: { width: 360, height: 800 },
isMobile: true,
hasTouch: true,
},
},
{
name: 'chromium-768',
use: {
...devices['Desktop Chrome'],
viewport: { width: 768, height: 800 },
},
},
{
name: 'chromium-1920',
use: {
...devices['Desktop Chrome'],
viewport: { width: 1920, height: 800 },
},
},
]
// WebKit – 2 breakpoints
const projectsWebKit = [
{
name: 'webkit-360',
use: {
...devices['Desktop Safari'],
viewport: { width: 360, height: 800 },
isMobile: true,
hasTouch: true,
},
},
{
name: 'webkit-1920',
use: {
...devices['Desktop Safari'],
viewport: { width: 1920, height: 800 },
},
},
]
// Mobile Safari (iOS emulation) – 1 breakpoint
const projectsSafari = [
{
name: 'mobile-safari-iphone12',
use: { ...devices['iPhone 12'] },
},
]
// Firefox – 1 breakpoint
const projectsFirefox = [
{
name: 'firefox-1920',
use: {
...devices['Desktop Firefox'],
viewport: { width: 1920, height: 800 },
},
},
]
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
// retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
// workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: process.env.E2E_BASE_URL ?? 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
// We balance the number of breakpoints.
...projectsChromium,
...projectsWebKit,
...projectsSafari,
...projectsFirefox,
],
snapshotPathTemplate: '__screenshots__/{projectName}/{testFilePath}/{testName}{ext}',
expect: {
timeout: process.env.CI ? 10_000 : 5_000,
// toHaveScreenshot: {
// maxDiffPixelRatio: 0.02,
// },
},
})
================================================
FILE: e2e/stubs/index.ts
================================================
import { getProjectsStub } from './projects'
import { getUsersStub } from './users'
export { getProjectsStub, getUsersStub }
================================================
FILE: e2e/stubs/projects.ts
================================================
export const getProjectsStub = () => [
{
id: 'bd25e86b-4330-4c1e-bb53-0cea27a04a0f',
project_name: 'nxn',
project_owner: '232e68af-7273-40e2-8c05-105077bc5813',
team: ['f2a79978-aac3-499d-9fe0-c5abd0b7d195'],
status: 'in progress',
created_at: '2025-08-06T18:40:56.509144',
updated_at: null,
},
{
id: 'fcd516a7-203b-4f78-855e-5d1e1d1620c2',
project_name: 'A',
project_owner: '232e68af-7273-40e2-8c05-105077bc5813',
team: [
'3d613cac-fea4-46a1-8c76-31b932a6b62f',
'f2a79978-aac3-499d-9fe0-c5abd0b7d195',
'76566f6b-780b-4aa9-b5e5-ebae713c486e',
],
status: 'in progress',
created_at: '2025-08-06T14:20:19.463354',
updated_at: null,
},
]
================================================
FILE: e2e/stubs/users.ts
================================================
export const getUsersStub = () => [
{
id: '120feece-1484-417f-80cc-9012bbf67753',
email: 'dddaaa@email.com',
fullname: 'Dddd',
username: 'aaa',
role: 'owner',
projects: ['bd25e86b-4330-4c1e-bb53-0cea27a04a0f', 'fcd516a7-203b-4f78-855e-5d1e1d1620c2'],
active: true,
avatar: '',
notes: '',
created_at: '2025-08-06T18:43:50.158982',
updated_at: '2025-08-06T18:43:58.789814',
},
{
id: '232e68af-7273-40e2-8c05-105077bc5813',
email: 'new@gmail.com',
fullname: 'new',
username: 'new',
role: 'admin',
projects: [],
active: true,
avatar: '',
notes: 'dsada',
created_at: '2025-08-06T05:37:13.803107',
updated_at: '2025-08-06T13:21:07.140781',
},
{
id: '23524da7-faf4-4435-9c75-bd25d23fa5fc',
email: 'ZXZ@gamial.com',
fullname: 'zXZ',
username: 'sadas',
role: 'user',
projects: [],
active: false,
avatar: '',
notes: 'zxzz',
created_at: '2025-07-14T19:24:17.688488',
updated_at: '2025-07-17T14:20:23.969589',
},
{
id: 'f1c3dcc0-b6a8-4d7d-a887-a988c5fc08be',
email: 'kim52@example.com',
fullname: 'Amy Avila',
username: 'Amy Avila',
role: 'user',
projects: [],
active: false,
avatar: '',
notes: '',
created_at: '2025-07-05T13:54:39.732775',
updated_at: '2025-07-07T11:46:19.24038',
},
{
id: '0696e6f6-6ad2-4a52-8ec5-108172b9f613',
email: 'dylanwilliams@example.org',
fullname: 'Randy Ellis',
username: 'Randy Ellis',
role: 'admin',
projects: [],
active: false,
avatar: '',
notes: '',
created_at: '2025-07-05T13:53:45.174',
updated_at: '2025-07-24T08:24:33.091025',
},
{
id: '58d3d0d5-96d9-47c6-a21d-8b3d3ae52506',
email: 'allendominique@example.com',
fullname: 'Todd Carpenter',
username: 'Todd Carpenter',
role: 'user',
projects: ['b43934c2-1025-44e5-9ec1-e550fb0e5923', '24fd64f3-8faf-4f85-bdea-6dcbaf4e265b'],
active: false,
avatar: '',
notes: '',
created_at: '2025-07-05T13:52:50.195688',
updated_at: '2025-07-29T07:51:34.564706',
},
{
id: '1b519289-543c-4e46-8bc3-83722caf0338',
email: 'wilsonjay@example.com',
fullname: 'James Harvey',
username: 'James Harvey',
role: 'user',
projects: [
'0be37d30-1fdb-4d42-b58a-14c99aaaa77a',
'f8002cfd-5b18-4e07-9d18-b8cb5dd99f58',
'abf480ca-f7ef-4e30-870b-94df6ea8198b',
],
active: false,
avatar: '',
notes: 'aaa',
created_at: '2025-07-05T13:52:21.089963',
updated_at: '2025-08-04T15:00:21.080954',
},
{
id: 'f2a79978-aac3-499d-9fe0-c5abd0b7d195',
email: 'elizabeth64@example.com',
fullname: 'David Green',
username: 'David Green',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:52:10.353433',
updated_at: null,
},
{
id: '54ac5b62-0340-477c-8df7-26c096f3f58a',
email: 'james77@example.com',
fullname: 'Christopher Simon',
username: 'Christopher Simon',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:51:58.459987',
updated_at: null,
},
{
id: '76566f6b-780b-4aa9-b5e5-ebae713c486e',
email: 'sheilabarry@example.com',
fullname: 'Amanda Prince',
username: 'Amanda Prince',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:51:51.763316',
updated_at: null,
},
{
id: '7174de13-675c-4b8d-b4ae-95230f9ea2f1',
email: 'elizabeth94@example.org',
fullname: 'Robert Roberts',
username: 'Robert Roberts',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:51:48.141006',
updated_at: null,
},
{
id: 'e0cfcd3f-8db2-4af1-a18e-d66dc5affcc1',
email: 'david43@example.org',
fullname: 'Benjamin Hurley',
username: 'Benjamin Hurley',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:51:43.993791',
updated_at: null,
},
{
id: 'bd1ca921-1c21-4165-bab0-25841bd3a98c',
email: 'seanthornton@example.com',
fullname: 'Nancy Mann',
username: 'Nancy Mann',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:51:41.068249',
updated_at: null,
},
{
id: '1d9e0878-aea4-4937-b439-fc5f324dde85',
email: 'claytoncatherine@example.org',
fullname: 'Todd Adams',
username: 'Todd Adams',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:51:36.547494',
updated_at: null,
},
{
id: '9c80038b-bb28-4d50-aea2-c068a7972cf1',
email: 'singhleslie@example.net',
fullname: 'Jeremy Thomas',
username: 'Jeremy Thomas',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:51:29.627917',
updated_at: null,
},
{
id: 'f95f6ee7-b308-4dcd-aa6c-e71ff076b8bb',
email: 'hamiltonmaria@example.net',
fullname: 'Christine Winters',
username: 'Christine Winters',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:51:26.169697',
updated_at: null,
},
{
id: 'c28e2798-f29c-4554-815b-6285695c901a',
email: 'natalie50@example.net',
fullname: 'Chad Simmons',
username: 'Chad Simmons',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:51:23.403471',
updated_at: null,
},
{
id: '42b2b763-913a-47f6-991c-904756871bd0',
email: 'jasmine30@example.net',
fullname: 'Kaitlyn Campbell',
username: 'Kaitlyn Campbell',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:51:19.111898',
updated_at: null,
},
{
id: '590a99cd-da7b-4f09-89df-db331b09bb72',
email: 'yadams@example.com',
fullname: 'Richard Morgan',
username: 'Richard Morgan',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:51:13.15227',
updated_at: null,
},
{
id: 'fce120dc-cb23-49b7-966b-6e9d24399370',
email: 'kelly12@example.org',
fullname: 'Mark Jackson',
username: 'Mark Jackson',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:51:08.86113',
updated_at: null,
},
{
id: 'c9dd68ff-dada-48fc-9366-4ecad86d8c8b',
email: 'iserrano@example.net',
fullname: 'Megan Campbell',
username: 'Megan Campbell',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:51:04.994443',
updated_at: null,
},
{
id: 'f5904878-5ead-4aeb-a11a-3ba3f8dedf23',
email: 'albertgrant@example.net',
fullname: 'Johnny Hogan Jr.',
username: 'Johnny Hogan Jr.',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:51:00.63685',
updated_at: null,
},
{
id: '6e725ed5-4646-413c-9398-adc2fd5a3b90',
email: 'justinrivera@example.org',
fullname: 'Christopher Wolfe',
username: 'Christopher Wolfe',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:50:50.622951',
updated_at: null,
},
{
id: 'da829ef4-8561-4cb6-963e-4026b83fc0c9',
email: 'dmoore@example.com',
fullname: 'Charles Valentine',
username: 'Charles Valentine',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:50:47.536161',
updated_at: null,
},
{
id: 'a97daf98-d7a8-4528-af20-907f921c3ef1',
email: 'jessicanolan@example.com',
fullname: 'Karen Abbott',
username: 'Karen Abbott',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:50:38.762764',
updated_at: null,
},
{
id: 'a8c10bd1-b9d5-4dc7-ad8f-e4fc46e6dc76',
email: 'wvazquez@example.org',
fullname: 'Kristen Pearson',
username: 'Kristen Pearson',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:50:35.313072',
updated_at: null,
},
{
id: '7eff325e-c272-4aa0-8600-1b06e660764f',
email: 'zacharywaters@example.com',
fullname: 'Leon Montoya',
username: 'Leon Montoya',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:50:32.006549',
updated_at: null,
},
{
id: 'a03fb461-eed9-423c-95e8-fa3c588609c2',
email: 'ocarey@example.com',
fullname: 'Jack Scott',
username: 'Jack Scott',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:50:27.671787',
updated_at: null,
},
{
id: 'a2906eb3-bb60-41f8-bcf5-39e7bdf09591',
email: 'danielrose@example.net',
fullname: 'Danielle Nelson',
username: 'Danielle Nelson',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:50:19.623103',
updated_at: null,
},
{
id: 'ddc60b59-fd97-47f3-aa6f-fc9df75e96de',
email: 'rshelton@example.com',
fullname: 'Michael Soto',
username: 'Michael Soto',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:50:15.646614',
updated_at: null,
},
{
id: 'db30472a-3f95-4abd-8039-842e5c2be055',
email: 'usmith@example.net',
fullname: 'Roger Richardson',
username: 'Roger Richardson',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:50:11.33142',
updated_at: null,
},
{
id: '4b20e1bf-000f-45f3-a61e-aa0290276352',
email: 'bautistajackson@example.net',
fullname: 'Carrie Rios',
username: 'Carrie Rios',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:50:07.13715',
updated_at: null,
},
{
id: '1d8b60e5-4aeb-4b6c-b8ca-50eb61db5384',
email: 'paulacollins@example.com',
fullname: 'Brianna Clark',
username: 'Brianna Clark',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:50:03.561321',
updated_at: null,
},
{
id: '421c0730-6b69-469f-8c39-615858bae662',
email: 'daughertyjustin@example.org',
fullname: 'Katherine Medina',
username: 'Katherine Medina',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:49:59.872742',
updated_at: null,
},
{
id: '9a4f6fc9-d311-4e2d-a172-d24c352fd084',
email: 'ortizandrew@example.com',
fullname: 'Jessica Werner',
username: 'Jessica Werner',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:49:55.376772',
updated_at: null,
},
{
id: '099114a9-31a7-4e01-bc33-8e0acb9cf8cc',
email: 'annaanderson@example.org',
fullname: 'Dr. Jennifer Flowers',
username: 'Dr. Jennifer Flowers',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:49:50.886741',
updated_at: null,
},
{
id: '219617b4-9cb5-452e-8641-74f7e9694bf1',
email: 'vmaldonado@example.org',
fullname: 'Alicia Taylor',
username: 'Alicia Taylor',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:49:48.043751',
updated_at: null,
},
{
id: 'dc02f632-1395-4996-a081-ba2cbd1321ee',
email: 'danielle12@example.com',
fullname: 'Edward Vasquez',
username: 'Edward Vasquez',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:49:40.782005',
updated_at: null,
},
{
id: 'aac1e629-6278-4a9e-9c1a-0fdea2c494f0',
email: 'rgross@example.net',
fullname: 'Dr. Bailey Boyle',
username: 'Dr. Bailey Boyle',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:49:36.612685',
updated_at: null,
},
{
id: '1f3f4729-7e7f-4df2-847f-ae7e3456ecf0',
email: 'marybrown@example.com',
fullname: 'Warren Shelton',
username: 'Warren Shelton',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:49:33.683346',
updated_at: null,
},
{
id: '10aee355-9a59-490c-a713-5a561228faf8',
email: 'matthewmarquez@example.com',
fullname: 'Joshua Marshall',
username: 'Joshua Marshall',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:49:31.101564',
updated_at: null,
},
{
id: '4bfd116f-c99c-4bc1-b8ef-f5c93a586324',
email: 'crystalwoodward@example.org',
fullname: 'Joanne Jenkins',
username: 'Joanne Jenkins',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:49:26.496752',
updated_at: null,
},
{
id: '48d3798f-b78b-402a-8818-dbdb993f4640',
email: 'nathanperez@example.org',
fullname: 'Ashley Harris',
username: 'Ashley Harris',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:49:23.576774',
updated_at: null,
},
{
id: '31a875c7-95ca-478a-ab3f-b75f89be778f',
email: 'burkekatherine@example.net',
fullname: 'Deborah Mitchell',
username: 'Deborah Mitchell',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:49:19.433055',
updated_at: null,
},
{
id: 'c7ee677b-0e66-493a-a416-d584e46e8679',
email: 'starktracy@example.com',
fullname: 'James Hicks',
username: 'James Hicks',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:49:16.501063',
updated_at: null,
},
{
id: 'f8e192b8-9831-4dfa-a9f6-b2a68cf11d13',
email: 'kendrahebert@example.org',
fullname: 'Terry Allen',
username: 'Terry Allen',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:49:13.684865',
updated_at: null,
},
{
id: 'e19d39e6-6be6-406e-837d-9a433b877262',
email: 'aaron71@example.com',
fullname: 'Brittany Bass',
username: 'Brittany Bass',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:49:10.395335',
updated_at: null,
},
{
id: 'ab710b9e-27c9-4a11-b982-f9b316e7862c',
email: 'desireebrewer@example.net',
fullname: 'Kathryn Moore',
username: 'Kathryn Moore',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:49:07.227766',
updated_at: null,
},
{
id: 'a940376b-0d00-4739-9c35-4eb97e67f469',
email: 'vasquezjennifer@example.com',
fullname: 'Zachary Duran',
username: 'Zachary Duran',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:49:03.273903',
updated_at: null,
},
{
id: '9b749656-5282-447b-84d1-53d989a2337f',
email: 'jenniferthompson@example.net',
fullname: 'Billy Olsen',
username: 'Billy Olsen',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:48:59.475426',
updated_at: null,
},
{
id: '66c5af3b-1b25-46ca-abe2-d3c7f6106d3f',
email: 'walkercharles@example.org',
fullname: 'Albert Taylor',
username: 'Albert Taylor',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:48:56.356589',
updated_at: null,
},
{
id: '74a3aa73-0b70-436c-a3c7-083544164db3',
email: 'williamschristina@example.org',
fullname: 'Megan Holland',
username: 'Megan Holland',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:48:52.907923',
updated_at: null,
},
{
id: '02cc847a-918d-4f99-89af-4ed7ca75b71a',
email: 'marymartinez@example.org',
fullname: 'Crystal Chen',
username: 'Crystal Chen',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:48:49.815571',
updated_at: null,
},
{
id: '16182908-40af-4b5c-a51a-20624506fb15',
email: 'l@google.com',
fullname: 'l',
username: 'l',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:37:45.605808',
updated_at: null,
},
{
id: 'e94d7c35-df2c-469c-bfa7-415d74c029d6',
email: 'k@google.com',
fullname: 'k',
username: 'k',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:28:13.818783',
updated_at: null,
},
{
id: '26306668-dd17-4f74-a9f5-059288ce0afa',
email: 'j@google.com',
fullname: 'j',
username: 'j',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:28:05.24718',
updated_at: null,
},
{
id: '8bab6957-fe08-4174-ab8c-5ef7e00e48bd',
email: 'i@google.com',
fullname: 'i',
username: 'i',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T13:27:58.374866',
updated_at: null,
},
{
id: '5c309e30-c12a-4c41-a69a-d8bb98b40c20',
email: 'h@google.com',
fullname: 'h',
username: 'h',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T09:40:51.682894',
updated_at: null,
},
{
id: '79aaa655-7cd3-4bbb-ae62-c81eb71db1d5',
email: 'g@google.com',
fullname: 'g',
username: 'g',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T09:40:44.763892',
updated_at: null,
},
{
id: '7bb05e71-dcc6-4d9b-bbea-ea98984f33e5',
email: 'f@google.com',
fullname: 'f',
username: 'f',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T09:40:40.129946',
updated_at: null,
},
{
id: 'cd92699a-370d-4c91-811e-672b41c79333',
email: 'e@google.com',
fullname: 'e',
username: 'e',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T09:40:36.322122',
updated_at: null,
},
{
id: '44c77503-030b-44ce-8384-800dbea62725',
email: 'd@google.com',
fullname: 'd',
username: 'd',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T09:40:31.868109',
updated_at: null,
},
{
id: '9007c141-d033-49c2-ba7f-2856bc61cde8',
email: 'c@google.com',
fullname: 'c',
username: 'c',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T09:40:28.120347',
updated_at: null,
},
{
id: '9a64f120-ea68-421a-8921-d2d1db070bb4',
email: 'b@google.com',
fullname: 'b',
username: 'b',
role: 'user',
projects: [],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T09:40:24.214502',
updated_at: null,
},
{
id: '8a43e5a7-4882-4c50-94f5-44c47aa66d44',
email: 'a@google.com',
fullname: 'a',
username: 'a',
role: 'user',
projects: ['87014f12-e25f-49e9-b61b-2e1409026ec4'],
active: true,
avatar: '',
notes: '',
created_at: '2025-07-05T09:39:54.345229',
updated_at: '2025-07-05T13:27:25.402822',
},
]
================================================
FILE: e2e/tests/vuestic-admin.spec.ts
================================================
import { test } from '@playwright/test'
import { testApiMocksPage } from '../utils'
import { getProjectsStub, getUsersStub } from '../stubs'
const pageMocks: {
name: string
url: string
apiMocks?: Record<string, object>
}[] = [
{
name: 'Dashboard',
url: '/dashboard',
},
{
name: 'Settings',
url: '/settings',
},
{
name: 'Preferences',
url: '/preferences',
},
{
name: 'Users',
url: '/users',
apiMocks: {
'/projects': getProjectsStub,
'/users': getUsersStub,
},
},
{
name: 'Projects',
url: '/projects',
apiMocks: {
'/projects': getProjectsStub,
'/users': getUsersStub,
},
},
{
name: 'Payments',
url: '/payments',
},
{
name: 'Payment Methods',
url: '/payments/payment-methods',
},
{
name: 'Billing',
url: '/payments/billing',
},
{
name: 'Pricing Plans',
url: '/payments/pricing-plans',
},
{
name: 'FAQ',
url: '/faq',
},
{
name: 'Auth',
url: '/auth',
},
{
name: 'Login',
url: '/auth/login',
},
{
name: 'Sign Up',
url: '/auth/signup',
},
{
name: 'Recover Password',
url: '/auth/recover-password',
},
{
name: 'Recover Password Email',
url: '/auth/recover-password-email',
},
{
name: '404',
url: '/404',
},
]
for (const item of pageMocks) {
test(item.name, async ({ page }) => {
await testApiMocksPage({
page,
...item,
})
})
}
================================================
FILE: e2e/utils/index.ts
================================================
import { expect } from '@playwright/test'
import type { Page } from 'playwright-core'
export const testApiMocksPage = async (config: { page: Page; url: string; apiMocks?: Record<string, object> }) => {
const { page, url, apiMocks } = config
if (apiMocks && Object.keys(apiMocks).length > 0) {
for (const [routeShort, mockResponse] of Object.entries(apiMocks)) {
await page.route(`*/**/${routeShort}`, async (route) => {
await route.fulfill({ json: mockResponse })
})
}
}
await page.goto(url, { waitUntil: 'networkidle' })
await page.waitForSelector('#app', { state: 'visible' })
await expect(page).toHaveScreenshot({ fullPage: true })
}
================================================
FILE: index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,300..600,0,0"
/>
<link rel="icon" href="/favicon.ico" />
<title>Vuestic Admin</title>
<style>
/* Set vuestic higher priority over tailwind normalize css */
@layer tailwind.base, vuestic;
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
================================================
FILE: netlify.toml
================================================
[build]
publish = "dist"
# Default build command.
command = "yarn build:ci"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
================================================
FILE: package.json
================================================
{
"name": "vuestic-admin",
"private": true,
"version": "3.1.0",
"type": "module",
"workspaces": [
"e2e"
],
"scripts": {
"prepare": "husky install",
"dev": "vite",
"build": "npm run lint && vue-tsc --noEmit && vite build",
"build:ci": "vite build",
"start:ci": "serve -s ./dist",
"prelint": "npm run format",
"lint": "eslint \"./src/**/*.{ts,js,vue}\" --fix",
"format": "prettier --write .",
"preview": "vite preview",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"e2e": "yarn workspace e2e test",
"e2e:update-snapshots": "yarn workspace e2e update"
},
"lint-staged": {
"./src/**/*.{ts,js,vue}": [
"npm run lint"
]
},
"dependencies": {
"@gtm-support/vue-gtm": "^2.0.0",
"@vuestic/compiler": "0.2.9",
"@vuestic/tailwind": "^0.1.3",
"@vueuse/core": "^10.6.1",
"chart.js": "^4.4.1",
"chartjs-chart-geo": "^4.2.8",
"epic-spinners": "^2.0.0",
"flag-icons": "^6.15.0",
"ionicons": "^4.6.3",
"medium-editor": "^5.23.3",
"pinia": "^2.1.7",
"register-service-worker": "^1.7.1",
"sass": "^1.69.5",
"serve": "^14.2.1",
"uuid": "^11.0.3",
"vue": "3.5.8",
"vue-chartjs": "^5.3.0",
"vue-i18n": "^9.6.2",
"vue-router": "^4.2.5",
"vuestic-ui": "^1.10.2"
},
"devDependencies": {
"@intlify/unplugin-vue-i18n": "^1.5.0",
"@storybook/addon-essentials": "^7.4.6",
"@storybook/addon-interactions": "^7.4.6",
"@storybook/addon-links": "^7.4.6",
"@storybook/blocks": "^7.4.6",
"@storybook/testing-library": "^0.2.2",
"@storybook/vue3": "^7.4.6",
"@storybook/vue3-vite": "^7.4.6",
"@types/medium-editor": "^5.0.5",
"@types/node": "^20.9.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/eslint-config-prettier": "^8.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"autoprefixer": "^10.4.13",
"eslint": "^8.13.0",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-storybook": "^0.6.15",
"eslint-plugin-vue": "^9.18.1",
"husky": "^8.0.1",
"lint-staged": "^15.1.0",
"postcss": "^8.4.21",
"prettier": "^3.1.0",
"storybook": "^7.4.6",
"tailwindcss": "^3.4.0",
"typescript": "^5.2.2",
"vite": "^5.4.9",
"vue-eslint-parser": "^9.3.2",
"vue-tsc": "^2.1.6"
},
"packageManager": "yarn@4.9.2"
}
================================================
FILE: postcss.config.js
================================================
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
================================================
FILE: public/site.webmanifest
================================================
{"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"}
================================================
FILE: src/App.vue
================================================
<template>
<RouterView />
</template>
<style lang="scss">
#app {
font-family: 'Inter', Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
min-width: 20rem;
}
</style>
================================================
FILE: src/components/NotFoundImage.vue
================================================
<template>
<svg fill="none" height="200" viewBox="0 0 200 200" width="200" xmlns="http://www.w3.org/2000/svg">
<path
clip-rule="evenodd"
d="M141.03 63.87c2.58-3.14 5.5-6.4 7.4-8.46l-1.4-1.3a215.73 215.73 0 0 0-7.47 8.55 90.25 90.25 0 0 0-3.51 4.5 17.33 17.33 0 0 0-2.08 3.46l1.79.67c.23-.63.87-1.68 1.83-3.02a87.85 87.85 0 0 1 3.44-4.4ZM94.82 51.4c2-2.4 4.27-4.9 5.74-6.46l-1.39-1.3a166.85 166.85 0 0 0-5.81 6.53 69.06 69.06 0 0 0-2.74 3.46 13.4 13.4 0 0 0-1.65 2.7l1.78.68c.18-.45.65-1.23 1.4-2.26.73-1 1.66-2.16 2.67-3.35Z"
fill="var(--va-danger)"
fill-rule="evenodd"
/>
<path
clip-rule="evenodd"
d="M140.87 63.63a82.26 82.26 0 0 1 6.96 4.97l1.2-1.48a84.18 84.18 0 0 0-7.13-5.1c-2.88-1.83-6.02-3.6-8.41-4.36l-.58 1.82c2.13.67 5.1 2.32 7.96 4.15ZM94.05 50.65c1.9 1.73 3.85 3.5 5.14 4.79l1.35-1.35a165.6 165.6 0 0 0-5.49-5.1c-1.81-1.65-3.51-3.19-4.51-4.19l-1.35 1.35c1.04 1.03 2.77 2.6 4.56 4.23l.3.27ZM116 88.33l.15.28c.65 1.2 1.19 2.47 1.72 3.78l.16.38c.48 1.18.97 2.38 1.56 3.55.12.24.25.5.4.7.14.22.33.44.58.6.59.38 1.23.28 1.85-.03a20.32 20.32 0 0 0 3.98-2.82c1.14-.96 2.22-1.86 3.45-2.52a.72.72 0 0 0-.68-1.26c-1.36.74-2.54 1.72-3.65 2.65l-.15.13c-1.18.98-2.3 1.9-3.59 2.54-.2.1-.32.12-.37.13-.04 0-.05 0-.06-.02a.72.72 0 0 1-.18-.2c-.1-.13-.19-.3-.3-.54a50.44 50.44 0 0 1-1.67-3.83 39.3 39.3 0 0 0-1.8-3.93l-.14-.25c-.37-.69-.92-1.72-1.6-2.43-.39-.42-.9-.8-1.51-.93a2.25 2.25 0 0 0-1.92.59c-.62.52-1.22 1.09-1.8 1.64l-.66.62c-.8.75-1.6 1.44-2.46 2-1.4.9-2.66.83-3.86.3-1.26-.57-2.45-1.66-3.62-2.83l-.05-.05c-.44-.44-.84-.84-1.22-1.16-.38-.32-.8-.6-1.27-.73-1.03-.29-1.96.24-2.99 1.15-.15.13-.3.32-.45.5l-.5.67-.26.38-.9 1.24c-.42.55-.81 1.02-1.14 1.32-.15.13-.25.2-.32.24A18.4 18.4 0 0 1 86.39 87l-.17-.17a6.66 6.66 0 0 0-.82-.75c-.27-.2-.7-.46-1.2-.4-.6.08-.95.53-1.14 1.02a234.7 234.7 0 0 0-1.76 5.34l-.02.07v.13a.72.72 0 0 0 1.41.11 7.87 7.87 0 0 1 .2-.65 230.75 230.75 0 0 1 1.53-4.56l.14.1a5.35 5.35 0 0 1 .76.72 19.49 19.49 0 0 0 4.9 3.57c.38.19.77.1 1.04-.01.27-.12.52-.3.74-.5.44-.4.9-.96 1.33-1.52a112.24 112.24 0 0 1 1.65-2.28c.14-.18.23-.28.27-.31.99-.88 1.4-.92 1.66-.84.18.05.4.17.74.45.32.27.68.64 1.16 1.1v.02c1.17 1.17 2.53 2.43 4.06 3.12a5.2 5.2 0 0 0 5.2-.4c.99-.63 1.86-1.4 2.67-2.15l.7-.66c.57-.55 1.13-1.07 1.7-1.56.35-.29.58-.3.73-.27.2.04.46.18.76.5.53.56.99 1.4 1.37 2.11Zm-31.53-1.27v.01ZM82 91.5Z"
fill="var(--va-danger)"
fill-rule="evenodd"
/>
<path
d="M128.72 17.58c10.44 3.5 21.51 8.9 29.59 16.56 8.1 7.69 14.21 17.09 17.58 26.53 3.38 9.44 3.99 18.86 1.2 26.64-1.36 3.8-4.73 7.68-9.2 11.44-4.47 3.74-9.94 7.28-15.4 10.42a234.47 234.47 0 0 1-15.23 7.92c-1.98.95-3.75 1.77-5.2 2.44-1.69.8-2.94 1.38-3.55 1.7-2.26 1.22-4.55 2.82-6.13 5.04a11.17 11.17 0 0 0-1.82 8.65c.45 2.74 2.96 12.33 5.88 23.12 2.93 10.82 6.3 22.94 8.51 30.79l1.55-.44a3483.9 3483.9 0 0 1-10.24-37.22c3.95.12 12.67.68 24.49 2.3 8.19 1.12 15.55 5.5 21.35 10.1a96.82 96.82 0 0 1 9.54 8.8c-1.56 2.16-2.97 5.26-3.96 9.6l1.56.35c.92-4.05 2.19-6.86 3.52-8.78l1.12 1.13c.92.89 1.69 1.5 2.34 1.82.66.33 1.37.46 2 .1.57-.33.84-.94 1-1.43.17-.53.28-1.15.38-1.73l-1.58-.28c-.1.61-.2 1.12-.33 1.52-.13.42-.25.52-.27.53h-.06c-.07 0-.2-.03-.42-.14a8.85 8.85 0 0 1-1.94-1.54c-.33-.32-.74-.74-1.24-1.26 2.27-2.52 4.56-2.84 5.66-2.6l.34-1.57c-1.82-.4-4.57.22-7.1 3.02a98.22 98.22 0 0 0-9.57-8.8c-5.9-4.68-13.53-9.26-22.13-10.44-12.34-1.69-21.34-2.23-25.13-2.31-1.92-7.24-3.37-12.95-3.7-14.9a9.57 9.57 0 0 1 1.56-7.46c1.37-1.93 3.41-3.39 5.58-4.56.58-.3 1.75-.85 3.38-1.6v-.01c1.44-.67 3.24-1.5 5.3-2.5 4.36-2.08 9.82-4.81 15.33-7.97 5.5-3.16 11.07-6.76 15.63-10.58 4.55-3.82 8.18-7.93 9.69-12.13 2.95-8.23 2.26-18.05-1.2-27.72-3.46-9.7-9.71-19.3-18-27.15-8.3-7.88-19.61-13.39-30.17-16.92-10.55-3.52-20.44-5.11-25.98-5.1-11.24 0-18.88 8.25-20.84 10.89-.4.54-.98 1.23-1.68 2.07l-.3.35c-.83.99-1.78 2.14-2.73 3.4-1.9 2.5-3.82 5.51-4.71 8.66-.88 3.08-2.13 12.09-3.16 20.86-1.03 8.8-1.86 17.52-1.86 20.08 0 2.07-.08 5.83-.16 9.64a5654.21 5654.21 0 0 0-.16 8.35 45.88 45.88 0 0 0 .07 3.72V99c.1.98.3 2.93.67 5.07.42 2.4 1.1 5.16 2.17 7.06a7.85 7.85 0 0 0 1.49 1.7l.06.05c.5.49 1.05 1.02 1.61 1.67a13.02 13.02 0 0 1 3.1 6.26c.27 1.4.22 3.4-.06 5.69a68.08 68.08 0 0 1-1.34 7.17 74.77 74.77 0 0 1-2.99 10c-.15.3-.5 1.15-.98 2.4-.55-.32-1.2-.82-1.9-1.5a33.24 33.24 0 0 1-3.3-3.81 117.54 117.54 0 0 1-7.13-10.88 313.14 313.14 0 0 1-7.44-13.39l-.71-1.32a35.6 35.6 0 0 0-1.1-1.97c-2.35-3.53-9.53-5.24-21.71-8.09.75-.82.72-.91 1.65-1.9 2.44-2.02 6.48.36 6.96.84l.48-.95c-2.86-2.39-6.99-2.3-8.34-.96-2.14 2.15-.74 1-3.1 2.97-.7.6-1.89 1.5-2.45 1.85-.55.34-.95.5-1.21.53h-.04a9.43 9.43 0 0 1-2.81-1.94l-1.1 1c.46.46 1 .93 1.62 1.41.44.34.87.62 1.25.82.19.1.38.18.57.23.17.06.4.1.64.08a4.9 4.9 0 0 0 1.91-.76 18.02 18.02 0 0 0 2.75-2.1c10.54 2.34 19.25 4.34 21.6 7.86a36.64 36.64 0 0 1 1.3 2.37l.42.78 1.35 2.52c1.65 3.06 3.8 7 6.13 10.93 2.33 3.94 4.85 7.91 7.23 11.04a34.86 34.86 0 0 0 3.47 4c.81.78 1.63 1.42 2.43 1.84a531.4 531.4 0 0 0-4.68 12.65c-1.4 3.97-2.8 8.05-3.86 11.48a90.14 90.14 0 0 0-1.3 4.6c-.32 1.29-.54 2.38-.58 3.15l1.6.09c.04-.6.22-1.57.54-2.86.31-1.27.75-2.8 1.28-4.5 1.05-3.4 2.43-7.47 3.83-11.43 2.82-7.94 5.7-15.4 6.18-16.4.53-1.12 1.96-5.5 3.1-10.32a69.6 69.6 0 0 0 1.37-7.34c.28-2.33.36-4.53.04-6.19-1.41-4.31-1.86-5.26-5.36-8.96a6.58 6.58 0 0 1-1.08-1.2c-.93-1.64-1.56-4.16-1.98-6.55a67.1 67.1 0 0 1-.66-4.95l-.03-.35c-.04-.35-.05-1.5-.03-3.21a691.73 691.73 0 0 1 .16-8.33c.08-3.8.16-7.59.16-9.68 0-2.44.81-11.07 1.85-19.89 1.03-8.83 2.27-17.68 3.1-20.61.82-2.86 2.6-5.68 4.46-8.14a80.8 80.8 0 0 1 2.68-3.33l.3-.35a48.6 48.6 0 0 0 1.74-2.15c1.85-2.5 9.08-10.23 19.55-10.24 5.32-.01 15.03 1.53 25.47 5.02Z"
fill="var(--va-danger)"
/>
<path
clip-rule="evenodd"
d="M24.85 101.92c.46 1.39 1.12 2.63 1.6 3.36l1.6-1.03a13.9 13.9 0 0 1-1.4-2.94 6.75 6.75 0 0 1-.38-3.48l-1.86-.43a8.59 8.59 0 0 0 .44 4.52Z"
fill="var(--va-danger)"
fill-rule="evenodd"
/>
</svg>
</template>
================================================
FILE: src/components/VuesticLogo.stories.ts
================================================
import VuesticLogo from './VuesticLogo.vue'
export default {
title: 'VuesticLogo',
component: VuesticLogo,
tags: ['autodocs'],
}
export const Default = () => ({
components: { VuesticLogo },
template: `<VuesticLogo start="#6B7AFE" end="#083CC6" />`,
})
export const White = () => ({
components: { VuesticLogo },
template: `<div class="bg-primary">
<VuesticLogo start="#FFF"/>
</div>`,
})
export const Blue = () => ({
components: { VuesticLogo },
template: `<VuesticLogo start="#0E41C9"/>`,
})
export const Height = () => ({
components: { VuesticLogo },
template: `<VuesticLogo start="#6B7AFE" end="#083CC6" :height="48"/>`,
})
================================================
FILE: src/components/VuesticLogo.vue
================================================
<template>
<svg :height="height" alt="Vuestic Admin" fill="none" viewBox="0 0 478 57" xmlns="http://www.w3.org/2000/svg">
<path
d="M18.2761 56.1386L0 13.3794H10.5519L18.8968 33.3107L27.2417 13.3794H37.7936L19.5175 56.1386H18.2761ZM62.4836 40.8281V13.3794H72.5527V39.3108C72.5527 44.8281 75.1734 46.7592 78.6907 46.7592C82.5528 46.7592 84.9666 44.8281 84.9666 39.3108V13.3794H95.1047V40.8281C95.1047 50.6213 87.7942 56.1386 78.6907 56.1386C69.2423 56.1386 62.4836 50.3454 62.4836 40.8281ZM122.415 54.7593V13.3794H149.795V22.3451H132.553V29.7935H148.485V38.6211H132.553V46.0695H150.554V54.7593H122.415ZM175.933 42.6212H185.795C185.795 45.035 187.727 46.9661 190.071 46.9661C192.623 46.9661 194.209 45.5178 194.209 43.3798C194.209 40.2763 190.14 39.3798 186.761 38.2074C179.795 35.7246 175.933 32.4831 175.933 25.3796C175.933 18.0692 182.209 12.0001 190.002 12.0001C199.175 12.0001 203.52 17.8623 204.072 25.5175H194.554C194.554 23.1727 193.106 21.2416 190.278 21.2416C188.071 21.2416 185.933 22.552 185.933 25.2417C185.933 28.3452 189.727 28.8969 193.244 29.9314C200.623 32.1383 204.072 36.2763 204.072 42.7591C204.072 50.0696 197.865 56.1386 190.071 56.1386C181.037 56.1386 175.933 50.0696 175.933 42.6212ZM236.442 54.7593V22.3451H227.2V13.3794H256.028V22.3451H246.649V54.7593H236.442ZM281.408 54.7593V13.3794H291.546V54.7593H281.408ZM317.502 34.0694C317.502 21.7244 326.812 12.0001 339.916 12.0001C347.778 12.0001 353.226 14.9657 357.64 19.5864L350.675 26.2072C347.778 23.3796 344.054 21.5865 339.916 21.5865C332.536 21.5865 327.778 27.1038 327.778 34.0694C327.778 41.035 332.536 46.5523 339.916 46.5523C344.054 46.5523 347.778 44.7592 350.675 41.9315L357.433 48.5523C353.295 52.8972 347.571 56.1386 339.916 56.1386C326.812 56.1386 317.502 46.4143 317.502 34.0694Z"
fill="url(#paint0_linear)"
/>
<path
:fill="colorsComputed.start"
d="M388.671 23.5893L388.364 24.844H391.71L391.403 23.5893C391.18 22.7157 390.957 21.7677 390.734 20.7454C390.511 19.7231 390.288 18.7565 390.065 17.8457H389.953C389.749 18.7751 389.535 19.751 389.312 20.7733C389.107 21.777 388.894 22.7157 388.671 23.5893ZM381.7 32.3999L387.221 14.2769H393.02L398.541 32.3999H393.466L392.574 28.5523H387.5L386.607 32.3999H381.7Z"
/>
<path
:fill="colorsComputed.start"
d="M403.987 32.3999V14.2769H409.34C410.715 14.2769 411.97 14.4441 413.104 14.7787C414.256 15.1133 415.241 15.6431 416.059 16.368C416.877 17.0929 417.509 18.0223 417.955 19.1561C418.42 20.29 418.652 21.6562 418.652 23.2548C418.652 24.8533 418.429 26.2288 417.983 27.3812C417.537 28.5337 416.905 29.4817 416.087 30.2252C415.288 30.9687 414.331 31.517 413.215 31.8702C412.119 32.2234 410.92 32.3999 409.619 32.3999H403.987ZM408.782 28.5523H409.061C409.712 28.5523 410.325 28.4779 410.901 28.3292C411.477 28.1619 411.97 27.8831 412.379 27.4928C412.806 27.1024 413.141 26.5634 413.383 25.8756C413.624 25.1879 413.745 24.3143 413.745 23.2548C413.745 22.1953 413.624 21.3309 413.383 20.6618C413.141 19.9926 412.806 19.4721 412.379 19.1004C411.97 18.7286 411.477 18.4777 410.901 18.3476C410.325 18.1989 409.712 18.1245 409.061 18.1245H408.782V28.5523Z"
/>
<path
:fill="colorsComputed.start"
d="M424.664 32.3999V14.2769H429.794L432.526 21.6934C432.694 22.1953 432.852 22.7157 433 23.2548C433.149 23.7938 433.307 24.3328 433.474 24.8719H433.586C433.753 24.3328 433.911 23.7938 434.06 23.2548C434.208 22.7157 434.366 22.1953 434.534 21.6934L437.155 14.2769H442.285V32.3999H437.935V27.0467C437.935 26.582 437.954 26.0708 437.991 25.5132C438.047 24.9555 438.103 24.3886 438.158 23.8124C438.214 23.2176 438.279 22.6414 438.354 22.0837C438.428 21.5261 438.493 21.0242 438.549 20.5781H438.437L436.932 24.9277L434.701 30.9222H432.136L429.905 24.9277L428.456 20.5781H428.344C428.4 21.0242 428.465 21.5261 428.539 22.0837C428.614 22.6414 428.679 23.2176 428.734 23.8124C428.79 24.3886 428.837 24.9555 428.874 25.5132C428.93 26.0708 428.957 26.582 428.957 27.0467V32.3999H424.664Z"
/>
<path :fill="colorsComputed.start" d="M450.028 32.3999V14.2769H454.823V32.3999H450.028Z" />
<path
:fill="colorsComputed.start"
d="M462.567 32.3999V14.2769H467.474L471.489 22.3625L473.218 26.4333H473.329C473.274 25.95 473.209 25.4202 473.134 24.844C473.06 24.2678 472.995 23.6823 472.939 23.0875C472.883 22.4741 472.828 21.87 472.772 21.2752C472.735 20.6618 472.716 20.0762 472.716 19.5186V14.2769H477.289V32.3999H472.381L468.366 24.2864L466.638 20.2435H466.526C466.638 21.2845 466.768 22.4183 466.917 23.6451C467.065 24.8719 467.14 26.0429 467.14 27.1582V32.3999H462.567Z"
/>
<defs>
<linearGradient id="paint0_linear" gradientUnits="userSpaceOnUse" x1="0" x2="357.64" y1="56.1386" y2="56.1386">
<stop :stop-color="colorsComputed.end" />
<stop :stop-color="colorsComputed.start" offset="1" />
</linearGradient>
</defs>
</svg>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import { useColors } from 'vuestic-ui'
const { getColor } = useColors()
const props = withDefaults(
defineProps<{
height?: number
start?: string
end?: string
}>(),
{
height: 18,
start: 'primary',
end: undefined,
},
)
const colorsComputed = computed(() => {
return {
start: getColor(props.start),
end: getColor(props.end || props.start),
}
})
</script>
================================================
FILE: src/components/app-layout-navigation/AppLayoutNavigation.vue
================================================
<template>
<div class="flex gap-2">
<VaIconMenuCollapsed
class="cursor-pointer"
:class="{ 'x-flip': !isSidebarMinimized }"
:color="collapseIconColor"
@click="isSidebarMinimized = !isSidebarMinimized"
/>
<nav class="flex items-center">
<VaBreadcrumbs>
<VaBreadcrumbsItem label="Home" :to="{ name: 'dashboard' }" />
<VaBreadcrumbsItem
v-for="item in items"
:key="item.label"
:label="item.label"
@click="handleBreadcrumbClick(item)"
/>
</VaBreadcrumbs>
</nav>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useColors } from 'vuestic-ui'
import VaIconMenuCollapsed from '../icons/VaIconMenuCollapsed.vue'
import { storeToRefs } from 'pinia'
import { useGlobalStore } from '../../stores/global-store'
import NavigationRoutes from '../sidebar/NavigationRoutes'
const { isSidebarMinimized } = storeToRefs(useGlobalStore())
const router = useRouter()
const route = useRoute()
const { t } = useI18n()
type BreadcrumbNavigationItem = {
label: string
to: string
hasChildren: boolean
}
const findRouteName = (name: string) => {
const traverse = (routers: any[]): string => {
for (const router of routers) {
if (router.name === name) {
return router.displayName
}
if (router.children) {
const result = traverse(router.children)
if (result) {
return result
}
}
}
return ''
}
return traverse(NavigationRoutes.routes)
}
const items = computed(() => {
const result: { label: string; to: string; hasChildren: boolean }[] = []
route.matched.forEach((route) => {
const labelKey = findRouteName(route.name as string)
if (!labelKey) {
return
}
result.push({
label: t(labelKey),
to: route.path,
hasChildren: route.children && route.children.length > 0,
})
})
return result
})
const { getColor } = useColors()
const collapseIconColor = computed(() => getColor('secondary'))
const handleBreadcrumbClick = (item: BreadcrumbNavigationItem) => {
if (!item.hasChildren) {
router.push(item.to)
}
}
</script>
<style lang="scss" scoped>
.x-flip {
transform: scaleX(-100%);
}
</style>
================================================
FILE: src/components/icons/VaIconCleanCode.vue
================================================
<template>
<svg class="va-icon-clean-code" viewBox="0 0 56.02 50.34" xmlns="http://www.w3.org/2000/svg">
<defs />
<title>overview_icon_4</title>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<path class="cls-1" d="M38.23,16.17a10,10,0,1,0-17.67,6.42V47.5l7.33-5,8,5V22.58A10,10,0,0,0,38.23,16.17Z" />
<path
class="cls-2"
d="M28.23,0a13.15,13.15,0,0,0-9.17,22.6V50.34l8.87-6,9.46,5.92V22.6A13.15,13.15,0,0,0,28.23,0ZM34.4,44.79l-6.54-4.08-5.8,4V24.79a13.11,13.11,0,0,0,12.33,0ZM28.23,23.33A10.17,10.17,0,1,1,38.4,13.17,10.18,10.18,0,0,1,28.23,23.33Z"
/>
<path
class="cls-2"
d="M28.23,5.67a7.5,7.5,0,1,0,7.5,7.5A7.51,7.51,0,0,0,28.23,5.67Zm0,12a4.5,4.5,0,1,1,4.5-4.5A4.5,4.5,0,0,1,28.23,17.67Z"
/>
<polygon class="cls-2" points="9.51 15.11 0 24.61 9.51 34.12 11.63 32 4.24 24.61 11.63 17.23 9.51 15.11" />
<polygon
class="cls-2"
points="46.52 15.11 44.39 17.23 51.78 24.61 44.39 32 46.52 34.12 56.02 24.61 46.52 15.11"
/>
</g>
</g>
</svg>
</template>
<style lang="scss">
.va-icon-clean-code {
display: inline-block;
width: 56px;
height: 50px;
.cls-1 {
fill: #4ae387;
}
.cls-2 {
fill: #34495e;
}
}
</style>
================================================
FILE: src/components/icons/VaIconColor.vue
================================================
<template>
<svg
:fill="color"
class="va-icon-color"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M12 22C6.49 22 2 17.51 2 12S6.49 2 12 2s10 4.04 10 9c0 3.31-2.69 6-6 6h-1.77c-.28 0-.5.22-.5.5 0 .12.05.23.13.33.41.47.64 1.06.64 1.67 0 1.38-1.12 2.5-2.5 2.5zm0-18c-4.41 0-8 3.59-8 8s3.59 8 8 8c.28 0 .5-.22.5-.5 0-.16-.08-.28-.14-.35-.41-.46-.63-1.05-.63-1.65 0-1.38 1.12-2.5 2.5-2.5H16c2.21 0 4-1.79 4-4 0-3.86-3.59-7-8-7z"
/>
<circle cx="6.5" cy="11.5" r="1.5" />
<circle cx="9.5" cy="7.5" r="1.5" />
<circle cx="14.5" cy="7.5" r="1.5" />
<circle cx="17.5" cy="11.5" r="1.5" />
</svg>
</template>
<script lang="ts" setup>
withDefaults(
defineProps<{
color?: string
}>(),
{
color: 'inherit',
},
)
</script>
<style lang="scss">
.va-icon-color {
display: inline-block;
width: 24px;
height: 24px;
}
</style>
================================================
FILE: src/components/icons/VaIconDiscord.vue
================================================
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<g clip-path="url(#clip0_1086_1040)">
<path
d="M16.9308 3.64152C15.6342 3.04632 14.2658 2.62189 12.8599 2.37886C12.8471 2.37647 12.834 2.37818 12.8222 2.38373C12.8105 2.38928 12.8008 2.39839 12.7945 2.40977C12.6187 2.72252 12.4239 3.13044 12.2876 3.45102C10.7503 3.22086 9.22092 3.22086 7.71525 3.45102C7.57892 3.12327 7.37709 2.72252 7.2005 2.40977C7.19396 2.39865 7.18422 2.38976 7.17255 2.38426C7.16088 2.37876 7.14783 2.37691 7.13509 2.37894C5.72907 2.62141 4.36058 3.04581 3.06409 3.64144C3.05299 3.64618 3.04365 3.65424 3.03734 3.66452C0.444503 7.53819 -0.26583 11.3166 0.08267 15.0482C0.0836554 15.0573 0.0864643 15.0662 0.0909303 15.0742C0.0953964 15.0822 0.101429 15.0893 0.10867 15.0949C1.81934 16.3513 3.47642 17.1139 5.10275 17.6194C5.1154 17.6232 5.1289 17.623 5.14144 17.6189C5.15398 17.6148 5.16496 17.607 5.17292 17.5964C5.55759 17.0711 5.9005 16.5171 6.19459 15.9346C6.19863 15.9266 6.20094 15.9179 6.20136 15.909C6.20178 15.9 6.2003 15.8911 6.19703 15.8828C6.19375 15.8745 6.18875 15.8669 6.18236 15.8607C6.17596 15.8544 6.16831 15.8496 6.15992 15.8465C5.61592 15.6402 5.098 15.3886 4.59975 15.1029C4.59067 15.0976 4.58303 15.0901 4.57753 15.0811C4.57203 15.0721 4.56882 15.0619 4.56819 15.0514C4.56757 15.0409 4.56954 15.0304 4.57394 15.0208C4.57834 15.0112 4.58503 15.0029 4.59342 14.9965C4.69825 14.9179 4.80317 14.8363 4.90325 14.7537C4.91215 14.7464 4.92291 14.7416 4.93434 14.7401C4.94577 14.7386 4.9574 14.7402 4.96792 14.7449C8.24109 16.2394 11.7846 16.2394 15.0191 14.7449C15.0296 14.7399 15.0414 14.738 15.0529 14.7394C15.0645 14.7408 15.0755 14.7455 15.0845 14.7529C15.1847 14.8354 15.2895 14.9179 15.3952 14.9965C15.4036 15.0028 15.4103 15.0111 15.4148 15.0206C15.4192 15.0302 15.4213 15.0406 15.4207 15.0512C15.4202 15.0617 15.4171 15.0719 15.4117 15.0809C15.4062 15.0899 15.3987 15.0974 15.3897 15.1029C14.8912 15.394 14.3691 15.6425 13.8288 15.8457C13.8204 15.8489 13.8128 15.8538 13.8065 15.8602C13.8001 15.8665 13.7952 15.8742 13.792 15.8826C13.7888 15.891 13.7874 15.8999 13.7879 15.9089C13.7884 15.9179 13.7908 15.9266 13.7949 15.9346C14.0953 16.5163 14.4382 17.0703 14.8158 17.5956C14.8235 17.6064 14.8344 17.6146 14.847 17.6189C14.8596 17.6231 14.8732 17.6233 14.8859 17.6194C16.5201 17.1139 18.1772 16.3512 19.8878 15.0949C19.8952 15.0896 19.9013 15.0827 19.9058 15.0747C19.9103 15.0668 19.9131 15.058 19.9139 15.0489C20.3309 10.7348 19.2154 6.98736 16.9568 3.66527C16.9513 3.65448 16.9421 3.64611 16.9308 3.64152ZM6.68334 12.776C5.69792 12.776 4.88592 11.8713 4.88592 10.7602C4.88592 9.64919 5.68217 8.74444 6.68342 8.74444C7.69242 8.74444 8.4965 9.6571 8.48075 10.7603C8.48075 11.8713 7.6845 12.776 6.68334 12.776ZM13.329 12.776C12.3436 12.776 11.5316 11.8713 11.5316 10.7602C11.5316 9.64919 12.3278 8.74444 13.329 8.74444C14.338 8.74444 15.1421 9.6571 15.1263 10.7603C15.1263 11.8713 14.338 12.776 13.329 12.776Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_1086_1040">
<rect width="20" height="20" fill="white" />
</clipPath>
</defs>
</svg>
</template>
================================================
FILE: src/components/icons/VaIconFaster.vue
================================================
<template>
<svg class="va-icon-faster" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<!-- Generator: sketchtool 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
<title>62EBC3B8-A55C-4B01-95A2-52FB8EDD4150</title>
<defs />
<g id="symbols" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1">
<g id="icon-faster" fill="#34495E">
<g>
<path
id="A"
d="M17.748,19 L16.956,16.3 L12.942,16.3 L12.168,19 L8.928,19 L13.302,6.13 L16.614,6.13 L20.988,19 L17.748,19 Z M14.976,9.064 L14.94,9.064 C14.94,9.064 14.652,10.468 14.418,11.278 L13.68,13.78 L16.218,13.78 L15.498,11.278 C15.264,10.468 14.976,9.064 14.976,9.064 Z"
/>
<rect id="Rectangle-4" height="2" rx="1" width="5" x="3" y="11" />
<rect id="Rectangle-4-Copy" height="2" rx="1" width="6" x="4" y="7" />
<rect id="Rectangle-4" height="2" rx="1" width="4" x="2" y="15" />
</g>
</g>
</g>
</svg>
</template>
<style lang="scss">
.va-icon-faster {
width: 24px;
height: 24px;
}
</style>
================================================
FILE: src/components/icons/VaIconFree.vue
================================================
<template>
<svg class="va-icon-free" viewBox="0 0 44.99 51.04" xmlns="http://www.w3.org/2000/svg">
<defs />
<title>overview_icon_2</title>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<path
class="cls-1"
d="M1.08,28.21C1.08,13.62,8.38,6.29,19,6.29S37,13.69,37,28.21,29.66,50.54,19,50.54,1.08,42.8,1.08,28.21Zm23.56,0c0-11.3-2.58-16.9-5.62-16.9s-5.62,5.6-5.62,16.9S16,41.66,19,41.66,24.65,39.51,24.65,28.21Z"
/>
<line class="cls-2" x1="39.83" x2="39.83" y1="47.62" y2="50.96" />
<path
class="cls-3"
d="M18.73,9.64c-4.9,0-6.9,4.54-6.9,15.66,0,11.29,2.06,16.1,6.9,16.1s6.9-4.81,6.9-16.1C25.63,14.17,23.63,9.64,18.73,9.64Zm0,28.76c-1.07,0-3.9,0-3.9-13.1,0-12.66,2.64-12.66,3.9-12.66s3.9,0,3.9,12.66C22.63,38.4,19.8,38.4,18.73,38.4Z"
/>
<path
class="cls-3"
d="M42.9,43.74A3.76,3.76,0,0,1,40.17,45c-1.95,0-3.24-1.57-3.24-4.4s1.53-4.35,3.29-4.35a3.67,3.67,0,0,1,2.5,1.11l2.08-2.55A6.8,6.8,0,0,0,41.33,33V31h-3v2.12a7.09,7.09,0,0,0-1.64.63,43.71,43.71,0,0,0,.77-8.41c0-15.84-7-25.3-18.73-25.3S0,9.46,0,25.3,7.18,51,18.73,51A16.4,16.4,0,0,0,33.12,43.1,6.77,6.77,0,0,0,40,48.46a6.35,6.35,0,0,0,5-2.22ZM18.73,48C8.88,48,3,39.54,3,25.3S8.73,3,18.73,3s15.73,8.13,15.73,22.3S28.58,48,18.73,48Z"
/>
</g>
</g>
</svg>
</template>
<style lang="scss">
.va-icon-free {
display: inline-block;
width: 55px;
height: 47.8px;
.cls-1 {
fill: #4ae387;
}
.cls-2 {
fill: none;
stroke: #34495e;
stroke-miterlimit: 10;
stroke-width: 3px;
}
.cls-3 {
fill: #34495e;
}
}
</style>
================================================
FILE: src/components/icons/VaIconFresh.vue
================================================
<template>
<svg class="va-icon-fresh" viewBox="0 0 50.98 47.66" xmlns="http://www.w3.org/2000/svg">
<defs />
<title>overview_icon_5</title>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<path
class="cls-1"
d="M6,19C11,12.66,26.33,3,46.33,6c-3.67,17-8.67,26-8.67,26s-7,14-19.67-3.67C5.67,26.33,9,22.33,6,19Z"
/>
<path
class="cls-2"
d="M48.33.49l-.77,0c-11.22-1.88-30.21,1.46-39,9-3.38,2.89-5,6.11-4.69,9.59a11.06,11.06,0,0,0,4.77,8,11,11,0,0,0,6.24,1.82q.53,0,1.09,0A55.51,55.51,0,0,0,13.2,39.21C9.48,33.07,2.35,30.83,0,30.83v3c.12,0,12.18,1.95,12.5,13.54h0c0,.1,0,.19,0,.29h3a50.57,50.57,0,0,1,3.12-17.21c2.48,5.09,6.36,8,10.91,8.13,5.3.07,10.1-3.85,11.91-9.81.94-3.12,1.88-6.37,2.78-9.51C46.32,11.9,48.31,5,49.62,2.76L51,.49ZM10.28,24.61a8.06,8.06,0,0,1-3.45-5.73c-.19-2.47,1-4.85,3.65-7.08,5.9-5,17.25-8.12,27-8.7-8.35,4-15.7,12.31-20.23,22.51l-.38.1h0A8.82,8.82,0,0,1,10.28,24.61Zm31-6.18c-.9,3.13-1.82,6.37-2.76,9.47-1.4,4.62-4.95,7.69-8.86,7.69h-.13c-4-.07-7.45-3.43-9.33-9.09C25.83,14.27,35.67,4.94,45.92,3.65,44.6,7,43.06,12.39,41.33,18.42Z"
/>
</g>
</g>
</svg>
</template>
<style lang="scss">
.va-icon-fresh {
display: inline-block;
width: 51px;
height: 48px;
.cls-1 {
fill: #4ae387;
}
.cls-2 {
fill: #34495e;
}
}
</style>
================================================
FILE: src/components/icons/VaIconGitHub.vue
================================================
<template>
<svg viewBox="0 0 98 96" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
fill="currentColor"
/>
</svg>
</template>
================================================
FILE: src/components/icons/VaIconHideSidebar.vue
================================================
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path
d="M18.3516 2.8125H3.97663C3.53557 2.8125 3.17802 3.17005 3.17802 3.61111C3.17802 4.05217 3.53557 4.40972 3.97663 4.40972H18.3516C18.7927 4.40972 19.1502 4.05217 19.1502 3.61111C19.1502 3.17005 18.7927 2.8125 18.3516 2.8125Z"
fill="#767C88"
/>
<path
d="M10.3655 9.20139H18.3516C18.5634 9.20139 18.7666 9.28553 18.9163 9.4353C19.0661 9.58506 19.1502 9.78819 19.1502 10C19.1502 10.2118 19.0661 10.4149 18.9163 10.5647C18.7666 10.7145 18.5634 10.7986 18.3516 10.7986H10.3655C10.1537 10.7986 9.95058 10.7145 9.80081 10.5647C9.65105 10.4149 9.56691 10.2118 9.56691 10C9.56691 9.78819 9.65105 9.58506 9.80081 9.4353C9.95058 9.28553 10.1537 9.20139 10.3655 9.20139ZM2.37941 9.20139H6.37246C6.58427 9.20139 6.7874 9.28553 6.93717 9.4353C7.08693 9.58506 7.17107 9.78819 7.17107 10C7.17107 10.2118 7.08693 10.4149 6.93717 10.5647C6.7874 10.7145 6.58427 10.7986 6.37246 10.7986H2.37941C2.1676 10.7986 1.96447 10.7145 1.8147 10.5647C1.66493 10.4149 1.5808 10.2118 1.5808 10C1.5808 9.78819 1.66493 9.58506 1.8147 9.4353C1.96447 9.28553 2.1676 9.20139 2.37941 9.20139Z"
fill="#767C88"
/>
<path
d="M18.3516 15.5903H3.97663C3.53557 15.5903 3.17802 15.9478 3.17802 16.3889C3.17802 16.8299 3.53557 17.1875 3.97663 17.1875H18.3516C18.7927 17.1875 19.1502 16.8299 19.1502 16.3889C19.1502 15.9478 18.7927 15.5903 18.3516 15.5903Z"
fill="#767C88"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3.64583 7.60417C3.95771 7.29229 4.46336 7.29229 4.77524 7.60417C5.08712 7.91604 5.08712 8.4217 4.77524 8.73357L3.50881 10L4.77524 11.2664C5.08712 11.5783 5.08712 12.084 4.77524 12.3958C4.46336 12.7077 3.95771 12.7077 3.64583 12.3958L1.74342 10.4934C1.47091 10.2209 1.47091 9.77909 1.74342 9.50658L3.64583 7.60417Z"
fill="#767C88"
/>
</svg>
</template>
================================================
FILE: src/components/icons/VaIconMenu.vue
================================================
<template>
<svg class="va-icon-menu" height="18" viewBox="0 0 24 18" width="23" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="nonzero" transform="translate(1 -3)">
<path d="M0 0h24v24H0z" />
<rect :fill="color" height="2" rx="1" width="20" x="2" y="3" />
<path :fill="color" d="M11 11h10a1 1 0 0 1 0 2H11a1 1 0 0 1 0-2zM1 11h5a1 1 0 0 1 0 2H1a1 1 0 0 1 0-2z" />
<rect :fill="color" height="2" rx="1" width="20" x="2" y="19" />
<path :stroke="color" d="M4 9l-3 3 3 3" stroke-width="2" />
</g>
</svg>
</template>
<script lang="ts" setup>
withDefaults(
defineProps<{
color?: string
}>(),
{
color: 'inherit',
},
)
</script>
<style lang="scss">
.va-icon-menu {
display: inline-block;
width: 24px;
height: 24px;
}
</style>
================================================
FILE: src/components/icons/VaIconMenuCollapsed.vue
================================================
<template>
<svg class="va-icon-menu-collapsed" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="nonzero">
<path d="M0 0h24v24H0z" />
<rect :fill="color" height="2" rx="1" width="20" x="2" y="3" />
<path
:fill="color"
d="M3 11h10a1 1 0 0 1 0 2H3a1 1 0 0 1 0-2zM20.993 11l-2.7-2.7-1.414 1.414L18.164 11H16a1 1 0 0 0 0 2h2.179l-1.3 1.3 1.414 1.414L21.007 13A1 1 0 0 0 21 11h-.007z"
/>
<rect :fill="color" height="2" rx="1" width="20" x="2" y="19" />
</g>
</svg>
</template>
<script lang="ts" setup>
withDefaults(
defineProps<{
color?: string
}>(),
{
color: 'inherit',
},
)
</script>
<style lang="scss">
.va-icon-menu-collapsed {
display: inline-block;
width: 24px;
height: 24px;
}
</style>
================================================
FILE: src/components/icons/VaIconMessage.vue
================================================
<template>
<svg :fill="color" height="16" viewBox="0 0 20 16" width="20" xmlns="http://www.w3.org/2000/svg">
<path
d="M20 2c0-1.1-.9-2-2-2H2C.9 0 0 .9 0 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V2zm-2 0l-8 5-8-5h16zm0 12H2V4l8 5 8-5v10z"
fill-rule="nonzero"
/>
</svg>
</template>
<script lang="ts" setup>
withDefaults(
defineProps<{
color?: string
}>(),
{
color: 'inherit',
},
)
</script>
<style lang="scss">
.va-icon-message {
display: inline-block;
width: 24px;
height: 24px;
}
</style>
================================================
FILE: src/components/icons/VaIconNotification.vue
================================================
<template>
<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path
:fill="color"
d="M10 20c1.1 0 2-.9 2-2H8c0 1.1.9 2 2 2zm6-6V9c0-3.07-1.63-5.64-4.5-6.32V2c0-.83-.67-1.5-1.5-1.5S8.5 1.17 8.5 2v.68C5.64 3.36 4 5.92 4 9v5l-2 2v1h16v-1l-2-2zm-2 1H6V9c0-2.48 1.51-4.5 4-4.5s4 2.02 4 4.5v6zM5.58 2.08L4.15.65C1.75 2.48.17 5.3.03 8.5h2a8.445 8.445 0 0 1 3.55-6.42zM17.97 8.5h2c-.15-3.2-1.73-6.02-4.12-7.85l-1.42 1.43a8.495 8.495 0 0 1 3.54 6.42z"
/>
</svg>
</template>
<script lang="ts" setup>
withDefaults(
defineProps<{
color?: string
}>(),
{
color: 'inherit',
},
)
</script>
================================================
FILE: src/components/icons/VaIconResponsive.vue
================================================
<template>
<svg class="va-icon-responsive" viewBox="0 0 47.5 49" xmlns="http://www.w3.org/2000/svg">
<defs />
<title>overview_icon_3</title>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<polygon class="cls-1" points="37 26 37 7 11 7 11 18 3 18 3 46 11 46 15 46 30 46 37 46 45 46 45 26 37 26" />
<path class="cls-2" d="M40,19V0H8V11H0V49H47.5V19ZM3,46V14H8V46Zm34,0H11V3H37Zm7.5,0H40V22h4.5Z" />
<circle class="cls-2" cx="24" cy="41" r="2.67" />
</g>
</g>
</svg>
</template>
<style lang="scss">
.va-icon-responsive {
display: inline-block;
width: 47.5px;
height: 49px;
.cls-1 {
fill: #4ae387;
}
.cls-2 {
fill: #34495e;
}
}
</style>
================================================
FILE: src/components/icons/VaIconRich.vue
================================================
<template>
<svg class="va-icon-rich" viewBox="0 0 56.99 55" xmlns="http://www.w3.org/2000/svg">
<defs />
<title>overview_icon_6</title>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<rect class="cls-1" height="23" width="37.33" x="10.31" y="30.5" />
<path
class="cls-2"
d="M57,41.18l-7.85-16V24H8.81v1.11L0,41.11l2.63,1.45L8.81,31.33V55H49.15V32L54.3,42.5ZM46.15,52H11.81V27H46.15Z"
/>
<polygon class="cls-2" points="35.3 1.8 32.9 0 28.12 6.39 26.16 4.63 24.16 6.87 28.56 10.8 35.3 1.8" />
<polygon
class="cls-2"
points="22.3 12.46 19.9 10.67 15.12 17.05 13.16 15.3 11.16 17.54 15.56 21.47 22.3 12.46"
/>
<polygon
class="cls-2"
points="38.89 21.14 45.64 12.13 43.23 10.33 38.45 16.72 36.49 14.97 34.49 17.2 38.89 21.14"
/>
</g>
</g>
</svg>
</template>
<style lang="scss">
.va-icon-rich {
display: inline-block;
width: 57px;
height: 55px;
.cls-1 {
fill: #4ae387;
}
.cls-2 {
fill: #34495e;
}
}
</style>
================================================
FILE: src/components/icons/VaIconSlower.vue
================================================
<template>
<svg class="va-icon-slower" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<!-- Generator: sketchtool 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
<title>67046716-A590-445C-AC65-1EEF69089C00</title>
<defs />
<g id="symbols" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1">
<g id="icon-slower" fill="#34495E">
<g>
<path
id="A"
d="M16.82,18.87 L16.028,16.17 L12.014,16.17 L11.24,18.87 L8,18.87 L12.374,6 L15.686,6 L20.06,18.87 L16.82,18.87 Z M14.048,8.934 L14.012,8.934 C14.012,8.934 13.724,10.338 13.49,11.148 L12.752,13.65 L15.29,13.65 L14.57,11.148 C14.336,10.338 14.048,8.934 14.048,8.934 Z"
/>
<rect id="Rectangle-4" height="2" rx="1" width="2" x="5" y="11" />
<rect id="Rectangle-4-Copy" height="2" rx="1" width="3" x="6" y="7" />
<rect id="Rectangle-4" height="2" rx="1" width="2" x="4" y="15" />
</g>
</g>
</g>
</svg>
</template>
<style lang="scss">
.va-icon-slower {
width: 24px;
height: 24px;
}
</style>
================================================
FILE: src/components/icons/VaIconVue.vue
================================================
<template>
<svg class="va-icon-vue" viewBox="0 0 55.05 47.8" xmlns="http://www.w3.org/2000/svg">
<defs />
<title>overview_icon_1</title>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<polygon
class="cls-1"
points="27.75 21.19 18.48 6.18 4.78 6.18 27.75 42.92 50.89 6.18 36.13 6.18 27.75 21.19"
/>
<path
class="cls-2"
d="M33.08,0,27.44,9.76,21.84,0H0L27.43,47.8,55,0ZM27.43,15.77,34.81,3h4.6l-12,20.72L15.55,3h4.55ZM5.18,3h6.91L27.43,29.73,42.88,3h7L27.44,41.78Z"
/>
</g>
</g>
</svg>
</template>
<style lang="scss">
.va-icon-vue {
display: inline-block;
width: 55px;
height: 47.8px;
.cls-1 {
fill: #4ae387;
}
.cls-2 {
fill: #34495e;
}
}
</style>
================================================
FILE: src/components/icons/VaIconVuestic.vue
================================================
<template>
<svg class="va-icon-vuestic" height="31" viewBox="0 0 304 31" width="304" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient :id="'ORIGINAL'" x1="0%" y1="50%" y2="50%">
<stop offset="0%" stop-color="#4AE387" />
<stop offset="100%" stop-color="#C8EA13" />
</linearGradient>
<linearGradient :id="'CORPORATE'" x1="0%" y1="50%" y2="50%">
<stop offset="0%" stop-color="#74BBFF" />
<stop offset="100%" stop-color="#6E85E8" />
</linearGradient>
</defs>
<g fill="none" fill-rule="evenodd" transform="scale(2)">
<path
:fill="`url(#${themeGradientId})`"
d="M6.36,15.36 L-7.10542736e-15,0.48 L3.672,0.48 L6.576,7.416 L9.48,0.48 L13.152,0.48 L6.792,15.36 L6.36,15.36 Z M21.744,10.032 L21.744,0.48 L25.248,0.48 L25.248,9.504 C25.248,11.424 26.16,12.096 27.384,12.096 C28.728,12.096 29.568,11.424 29.568,9.504 L29.568,0.48 L33.096,0.48 L33.096,10.032 C33.096,13.44 30.552,15.36 27.384,15.36 C24.096,15.36 21.744,13.344 21.744,10.032 Z M42.6,14.88 L42.6,0.48 L52.128,0.48 L52.128,3.6 L46.128,3.6 L46.128,6.192 L51.672,6.192 L51.672,9.264 L46.128,9.264 L46.128,11.856 L52.392,11.856 L52.392,14.88 L42.6,14.88 Z M61.224,10.656 L64.656,10.656 C64.656,11.496 65.328,12.168 66.144,12.168 C67.032,12.168 67.584,11.664 67.584,10.92 C67.584,9.84 66.168,9.528 64.992,9.12 C62.568,8.256 61.224,7.128 61.224,4.656 C61.224,2.112 63.408,7.10542736e-15 66.12,7.10542736e-15 C69.312,7.10542736e-15 70.824,2.04 71.016,4.704 L67.704,4.704 C67.704,3.888 67.2,3.216 66.216,3.216 C65.448,3.216 64.704,3.672 64.704,4.608 C64.704,5.688 66.024,5.88 67.248,6.24 C69.816,7.008 71.016,8.448 71.016,10.704 C71.016,13.248 68.856,15.36 66.144,15.36 C63,15.36 61.224,13.248 61.224,10.656 Z M82.896,14.88 L82.896,3.6 L79.68,3.6 L79.68,0.48 L89.712,0.48 L89.712,3.6 L86.448,3.6 L86.448,14.88 L82.896,14.88 Z M98.544,14.88 L98.544,0.48 L102.072,0.48 L102.072,14.88 L98.544,14.88 Z M111.72,7.68 C111.72,3.384 114.96,7.10542736e-15 119.52,7.10542736e-15 C122.256,7.10542736e-15 124.152,1.032 125.688,2.64 L123.264,4.944 C122.256,3.96 120.96,3.336 119.52,3.336 C116.952,3.336 115.296,5.256 115.296,7.68 C115.296,10.104 116.952,12.024 119.52,12.024 C120.96,12.024 122.256,11.4 123.264,10.416 L125.616,12.72 C124.176,14.232 122.184,15.36 119.52,15.36 C114.96,15.36 111.72,11.976 111.72,7.68 Z"
fill-rule="nonzero"
/>
<path
:fill="textColor"
d="M139.712,7.88 L139.712,6.152 L138.44,6.152 C138.272,6.152 138.066,6.162 137.822,6.182 C137.578,6.202 137.36,6.224 137.168,6.248 C137.424,5.984 137.682,5.704 137.942,5.408 C138.202,5.112 138.436,4.808 138.644,4.496 C138.852,4.184 139.022,3.87 139.154,3.554 C139.286,3.238 139.352,2.928 139.352,2.624 C139.352,2.248 139.288,1.906 139.16,1.598 C139.032,1.29 138.852,1.028 138.62,0.812 C138.388,0.596 138.112,0.428 137.792,0.308 C137.472,0.188 137.12,0.128 136.736,0.128 C136.456,0.128 136.198,0.152 135.962,0.2 C135.726,0.248 135.502,0.322 135.29,0.422 C135.078,0.522 134.876,0.65 134.684,0.806 C134.492,0.962 134.296,1.144 134.096,1.352 L134.096,1.352 L135.2,2.456 C135.384,2.272 135.574,2.106 135.77,1.958 C135.966,1.81 136.192,1.736 136.448,1.736 C136.728,1.736 136.954,1.818 137.126,1.982 C137.298,2.146 137.384,2.4 137.384,2.744 C137.384,3 137.302,3.274 137.138,3.566 C136.974,3.858 136.748,4.166 136.46,4.49 C136.172,4.814 135.834,5.16 135.446,5.528 C135.058,5.896 134.64,6.288 134.192,6.704 L134.192,6.704 L134.192,7.88 L139.712,7.88 Z M142.56,8.024 C142.912,8.024 143.2,7.904 143.424,7.664 C143.648,7.424 143.76,7.128 143.76,6.776 C143.76,6.424 143.648,6.128 143.424,5.888 C143.2,5.648 142.912,5.528 142.56,5.528 C142.208,5.528 141.92,5.648 141.696,5.888 C141.472,6.128 141.36,6.424 141.36,6.776 C141.36,7.128 141.472,7.424 141.696,7.664 C141.92,7.904 142.208,8.024 142.56,8.024 Z M150.736,7.88 L150.736,6.224 L149.368,6.224 L149.368,0.272 L147.856,0.272 C147.568,0.456 147.272,0.604 146.968,0.716 C146.664,0.828 146.296,0.928 145.864,1.016 L145.864,1.016 L145.864,2.288 L147.304,2.288 L147.304,6.224 L145.672,6.224 L145.672,7.88 L150.736,7.88 Z"
/>
</g>
</svg>
</template>
<script>
export default {
name: 'VaIconVuestic',
inject: ['contextConfig'],
computed: {
themeGradientId() {
return this.contextConfig.invertedColor ? 'CORPORATE' : 'ORIGINAL'
},
textColor() {
return this.contextConfig.invertedColor ? '#6E85E8' : '#E4FF32'
},
},
}
</script>
<style lang="scss">
.va-icon-vuestic {
.st0 {
fill: #4ae387;
}
}
</style>
================================================
FILE: src/components/navbar/AppNavbar.vue
================================================
<template>
<VaNavbar class="app-layout-navbar py-2 px-0">
<template #left>
<div class="left">
<Transition v-if="isMobile" name="icon-fade" mode="out-in">
<VaIcon
color="primary"
:name="isSidebarMinimized ? 'menu' : 'close'"
size="24px"
style="margin-top: 3px"
@click="isSidebarMinimized = !isSidebarMinimized"
/>
</Transition>
<RouterLink to="/" aria-label="Visit home page">
<VuesticLogo />
</RouterLink>
</div>
</template>
<template #right>
<AppNavbarActions class="app-navbar__actions" :is-mobile="isMobile" />
</template>
</VaNavbar>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useGlobalStore } from '../../stores/global-store'
import AppNavbarActions from './components/AppNavbarActions.vue'
import VuesticLogo from '../VuesticLogo.vue'
defineProps({
isMobile: { type: Boolean, default: false },
})
const GlobalStore = useGlobalStore()
const { isSidebarMinimized } = storeToRefs(GlobalStore)
</script>
<style lang="scss" scoped>
.va-navbar {
z-index: 2;
@media screen and (max-width: 950px) {
.left {
width: 100%;
}
.app-navbar__actions {
display: flex;
justify-content: space-between;
}
}
}
.left {
display: flex;
align-items: center;
margin-left: 1rem;
& > * {
margin-right: 1rem;
}
& > *:last-child {
margin-right: 0;
}
}
.icon-fade-enter-active,
.icon-fade-leave-active {
transition: transform 0.5s ease;
}
.icon-fade-enter,
.icon-fade-leave-to {
transform: scale(0.5);
}
</style>
================================================
FILE: src/components/navbar/components/AppNavbarActions.vue
================================================
<template>
<div class="app-navbar-actions">
<VaButton
v-if="!isMobile"
preset="secondary"
href="https://ui.vuestic.dev/support/consulting"
target="_blank"
color="textPrimary"
class="app-navbar-actions__item flex-shrink-0 mx-0"
>
<VaIcon size="large" class="material-symbols-outlined mr-1">support_agent</VaIcon>
{{ t('supportAndConsulting') }}
</VaButton>
<VaButton
v-if="!isMobile"
preset="secondary"
href="https://admin.vuestic.dev/"
target="_blank"
color="textPrimary"
class="app-navbar-actions__item flex-shrink-0 mx-0"
>
<VaIcon size="large" class="material-symbols-outlined mr-1">info</VaIcon>
{{ t('aboutVuesticAdmin') }}
</VaButton>
<GithubButton v-if="!isMobile" class="app-navbar-actions__item" />
<VaButton
v-if="!isMobile"
preset="secondary"
href="https://discord.gg/u7fQdqQt8c"
target="_blank"
color="textPrimary"
class="app-navbar-actions__item flex-shrink-0 mx-0"
>
<VaIcon :component="VaIconDiscord" />
</VaButton>
<NotificationDropdown class="app-navbar-actions__item" />
<ProfileDropdown class="app-navbar-actions__item app-navbar-actions__item--profile mr-1" />
</div>
</template>
<script lang="ts" setup>
import ProfileDropdown from './dropdowns/ProfileDropdown.vue'
import NotificationDropdown from './dropdowns/NotificationDropdown.vue'
import GithubButton from './GitHubButton.vue'
import VaIconDiscord from '../../icons/VaIconDiscord.vue'
defineProps({
isMobile: { type: Boolean, default: false },
})
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
</script>
<style lang="scss">
.app-navbar-actions {
display: flex;
align-items: center;
.va-dropdown__anchor {
color: var(--va-primary);
fill: var(--va-primary);
}
&__item {
padding: 0;
margin-left: 0.25rem;
margin-right: 0.25rem;
svg {
height: 20px;
}
&--profile {
display: flex;
justify-content: center;
}
.va-dropdown-content {
background-color: var(--va-white);
}
@media screen and (max-width: 640px) {
margin-left: 0;
margin-right: 0;
&:first-of-type {
margin-left: 0;
}
}
}
.fa-github {
color: var(--va-on-background-primary);
}
}
</style>
================================================
FILE: src/components/navbar/components/GitHubButton.vue
================================================
<template>
<VaButton
preset="secondary"
color="textPrimary"
href="https://github.com/epicmaxco/vuestic-admin"
target="_blank"
aria-label="Visit github"
>
<VaIcon :component="VaIconGitHub" />
</VaButton>
</template>
<script lang="ts" setup>
import VaIconGitHub from '../../icons/VaIconGitHub.vue'
</script>
================================================
FILE: src/components/navbar/components/dropdowns/NotificationDropdown.vue
================================================
<template>
<VaDropdown :offset="[13, 0]" class="notification-dropdown" stick-to-edges :close-on-content-click="false">
<template #anchor>
<VaButton preset="secondary" color="textPrimary">
<VaBadge overlap>
<template #text> 2+</template>
<VaIconNotification class="notification-dropdown__icon" />
</VaBadge>
</VaButton>
</template>
<VaDropdownContent class="h-full sm:max-w-[420px] sm:h-auto">
<section class="sm:max-h-[320px] p-4 overflow-auto">
<VaList class="space-y-1 mb-2">
<template v-for="(item, index) in notificationsWithRelativeTime" :key="item.id">
<VaListItem class="text-base">
<VaListItemSection icon class="mx-0 p-0">
<VaIcon :name="item.icon" color="secondary" />
</VaListItemSection>
<VaListItemSection>
{{ item.message }}
</VaListItemSection>
<VaListItemSection icon class="mx-1">
{{ item.updateTimestamp }}
</VaListItemSection>
</VaListItem>
<VaListSeparator v-if="item.separator && index !== notificationsWithRelativeTime.length - 1" class="mx-3" />
</template>
</VaList>
<VaButton preset="primary" class="w-full" @click="displayAllNotifications = !displayAllNotifications"
>{{ displayAllNotifications ? t('notifications.less') : t('notifications.all') }}
</VaButton>
</section>
</VaDropdownContent>
</VaDropdown>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import VaIconNotification from '../../../icons/VaIconNotification.vue'
const { t, locale } = useI18n()
const baseNumberOfVisibleNotifications = 4
const rtf = new Intl.RelativeTimeFormat(locale.value, { style: 'short' })
const displayAllNotifications = ref(false)
interface INotification {
message: string
icon: string
id: number
separator?: boolean
updateTimestamp: Date
}
const makeDateFromNow = (timeFromNow: number) => {
const date = new Date()
date.setTime(date.getTime() + timeFromNow)
return date
}
const notifications: INotification[] = [
{
message: '4 pending requests',
icon: 'favorite_outline',
id: 1,
separator: true,
updateTimestamp: makeDateFromNow(-3 * 60 * 1000),
},
{
message: '3 new reports',
icon: 'calendar_today',
id: 2,
separator: true,
updateTimestamp: makeDateFromNow(-12 * 60 * 60 * 1000),
},
{
message: 'Whoops! Your trial period has expired.',
icon: 'error_outline',
id: 3,
separator: true,
updateTimestamp: makeDateFromNow(-2 * 24 * 60 * 60 * 1000),
},
{
message: 'It looks like your timezone is set incorrectly, please change it to avoid issues with Memory.',
icon: 'schedule',
id: 4,
updateTimestamp: makeDateFromNow(-2 * 7 * 24 * 60 * 60 * 1000),
},
{
message: '2 new team members added',
icon: 'group_add',
id: 5,
separator: false,
updateTimestamp: makeDateFromNow(-3 * 60 * 1000),
},
{
message: 'Monthly budget exceeded by 10%',
icon: 'trending_up',
id: 6,
separator: true,
updateTimestamp: makeDateFromNow(-3 * 24 * 60 * 60 * 1000),
},
{
message: '7 tasks are approaching their deadlines',
icon: 'alarm',
id: 7,
separator: false,
updateTimestamp: makeDateFromNow(-5 * 60 * 60 * 1000),
},
{
message: 'New software update available',
icon: 'system_update',
id: 8,
separator: true,
updateTimestamp: makeDateFromNow(-1 * 24 * 60 * 60 * 1000),
},
].sort((a, b) => new Date(b.updateTimestamp).getTime() - new Date(a.updateTimestamp).getTime())
const TIME_NAMES = {
second: 1000,
minute: 1000 * 60,
hour: 1000 * 60 * 60,
day: 1000 * 60 * 60 * 24,
week: 1000 * 60 * 60 * 24 * 7,
month: 1000 * 60 * 60 * 24 * 30,
year: 1000 * 60 * 60 * 24 * 365,
}
const getTimeName = (differenceTime: number) => {
return Object.keys(TIME_NAMES).reduce(
(acc, key) => (TIME_NAMES[key as keyof typeof TIME_NAMES] < differenceTime ? key : acc),
'month',
) as keyof typeof TIME_NAMES
}
const notificationsWithRelativeTime = computed(() => {
const list = displayAllNotifications.value ? notifications : notifications.slice(0, baseNumberOfVisibleNotifications)
return list.map((item, index) => {
const timeDifference = Math.round(new Date().getTime() - new Date(item.updateTimestamp).getTime())
const timeName = getTimeName(timeDifference)
let separator = false
const nextItem = list[index + 1]
if (nextItem) {
const nextItemDifference = Math.round(new Date().getTime() - new Date(nextItem.updateTimestamp).getTime())
const nextItemTimeName = getTimeName(nextItemDifference)
if (timeName !== nextItemTimeName) {
separator = true
}
}
return {
...item,
updateTimestamp: rtf.format(-1 * Math.round(timeDifference / TIME_NAMES[timeName]), timeName),
separator,
}
})
})
</script>
<style lang="scss" scoped>
.notification-dropdown {
cursor: pointer;
.notification-dropdown__icon {
position: relative;
display: flex;
align-items: center;
}
.va-dropdown__anchor {
display: inline-block;
}
}
</style>
================================================
FILE: src/components/navbar/components/dropdowns/ProfileDropdown.vue
================================================
<template>
<div class="profile-dropdown-wrapper">
<VaDropdown v-model="isShown" :offset="[9, 0]" class="profile-dropdown" stick-to-edges>
<template #anchor>
<VaButton preset="secondary" color="textPrimary">
<span class="profile-dropdown__anchor min-w-max">
<slot />
<VaAvatar :size="32" color="warning"> 😍 </VaAvatar>
</span>
</VaButton>
</template>
<VaDropdownContent
class="profile-dropdown__content md:w-60 px-0 py-4 w-full"
:style="{ '--hover-color': hoverColor }"
>
<VaList v-for="group in options" :key="group.name">
<header v-if="group.name" class="uppercase text-[var(--va-secondary)] opacity-80 font-bold text-xs px-4">
{{ t(`user.${group.name}`) }}
</header>
<VaListItem
v-for="item in group.list"
:key="item.name"
class="menu-item px-4 text-base cursor-pointer h-8"
v-bind="resolveLinkAttribute(item)"
>
<VaIcon :name="item.icon" class="pr-1" color="secondary" />
{{ t(`user.${item.name}`) }}
</VaListItem>
<VaListSeparator v-if="group.separator" class="mx-3 my-2" />
</VaList>
</VaDropdownContent>
</VaDropdown>
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useColors } from 'vuestic-ui'
const { colors, setHSLAColor } = useColors()
const hoverColor = computed(() => setHSLAColor(colors.focus, { a: 0.1 }))
const { t } = useI18n()
type ProfileListItem = {
name: string
to?: string
href?: string
icon: string
}
type ProfileOptions = {
name: string
separator: boolean
list: ProfileListItem[]
}
withDefaults(
defineProps<{
options?: ProfileOptions[]
}>(),
{
options: () => [
{
name: 'account',
separator: true,
list: [
{
name: 'profile',
to: 'preferences',
icon: 'mso-account_circle',
},
{
name: 'settings',
to: 'settings',
icon: 'mso-settings',
},
{
name: 'billing',
to: 'billing',
icon: 'mso-receipt_long',
},
{
name: 'projects',
to: 'projects',
icon: 'mso-favorite',
},
],
},
{
name: 'explore',
separator: true,
list: [
{
name: 'faq',
to: 'faq',
icon: 'mso-quiz',
},
{
name: 'helpAndSupport',
href: 'https://discord.gg/u7fQdqQt8c',
icon: 'mso-error',
},
],
},
{
name: '',
separator: false,
list: [
{
name: 'logout',
to: 'login',
icon: 'mso-logout',
},
],
},
],
},
)
const isShown = ref(false)
const resolveLinkAttribute = (item: ProfileListItem) => {
return item.to ? { to: { name: item.to } } : item.href ? { href: item.href, target: '_blank' } : {}
}
</script>
<style lang="scss">
.profile-dropdown {
cursor: pointer;
&__content {
.menu-item:hover {
background: var(--hover-color);
}
}
&__anchor {
display: inline-block;
}
}
</style>
================================================
FILE: src/components/sidebar/AppSidebar.vue
================================================
<template>
<VaSidebar v-model="writableVisible" :width="sidebarWidth" :color="color" minimized-width="0">
<VaAccordion v-model="value" multiple>
<VaCollapse v-for="(route, index) in navigationRoutes.routes" :key="index">
<template #header="{ value: isCollapsed }">
<VaSidebarItem
:to="route.children ? undefined : { name: route.name }"
:active="routeHasActiveChild(route)"
:active-color="activeColor"
:text-color="textColor(route)"
:aria-label="`${route.children ? 'Open category ' : 'Visit'} ${t(route.displayName)}`"
role="button"
hover-opacity="0.10"
>
<VaSidebarItemContent class="py-3 pr-2 pl-4">
<VaIcon
v-if="route.meta.icon"
aria-hidden="true"
:name="route.meta.icon"
size="20px"
:color="iconColor(route)"
/>
<VaSidebarItemTitle class="flex justify-between items-center leading-5 font-semibold">
{{ t(route.displayName) }}
<VaIcon v-if="route.children" :name="arrowDirection(isCollapsed)" size="20px" />
</VaSidebarItemTitle>
</VaSidebarItemContent>
</VaSidebarItem>
</template>
<template #body>
<div v-for="(childRoute, index2) in route.children" :key="index2">
<VaSidebarItem
:to="{ name: childRoute.name }"
:active="isActiveChildRoute(childRoute)"
:active-color="activeColor"
:text-color="textColor(childRoute)"
:aria-label="`Visit ${t(route.displayName)}`"
hover-opacity="0.10"
>
<VaSidebarItemContent class="py-3 pr-2 pl-11">
<VaSidebarItemTitle class="leading-5 font-semibold">
{{ t(childRoute.displayName) }}
</VaSidebarItemTitle>
</VaSidebarItemContent>
</VaSidebarItem>
</div>
</template>
</VaCollapse>
</VaAccordion>
</VaSidebar>
</template>
<script lang="ts">
import { defineComponent, watch, ref, computed } from 'vue'
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useColors } from 'vuestic-ui'
import navigationRoutes, { type INavigationRoute } from './NavigationRoutes'
export default defineComponent({
name: 'Sidebar',
props: {
visible: { type: Boolean, default: true },
mobile: { type: Boolean, default: false },
},
emits: ['update:visible'],
setup: (props, { emit }) => {
const { getColor, colorToRgba } = useColors()
const route = useRoute()
const { t } = useI18n()
const value = ref<boolean[]>([])
const writableVisible = computed({
get: () => props.visible,
set: (v: boolean) => emit('update:visible', v),
})
const isActiveChildRoute = (child: INavigationRoute) => route.name === child.name
const routeHasActiveChild = (section: INavigationRoute) => {
if (!section.children) {
return route.path.endsWith(`${section.name}`)
}
return section.children.some(({ name }) => route.path.endsWith(`${name}`))
}
const setActiveExpand = () =>
(value.value = navigationRoutes.routes.map((route: INavigationRoute) => routeHasActiveChild(route)))
const sidebarWidth = computed(() => (props.mobile ? '100vw' : '280px'))
const color = computed(() => getColor('background-secondary'))
const activeColor = computed(() => colorToRgba(getColor('focus'), 0.1))
const iconColor = (route: INavigationRoute) => (routeHasActiveChild(route) ? 'primary' : 'secondary')
const textColor = (route: INavigationRoute) => (routeHasActiveChild(route) ? 'primary' : 'textPrimary')
const arrowDirection = (state: boolean) => (state ? 'va-arrow-up' : 'va-arrow-down')
watch(() => route.fullPath, setActiveExpand, { immediate: true })
return {
writableVisible,
sidebarWidth,
value,
color,
activeColor,
navigationRoutes,
routeHasActiveChild,
isActiveChildRoute,
t,
iconColor,
textColor,
arrowDirection,
}
},
})
</script>
================================================
FILE: src/components/sidebar/NavigationRoutes.ts
================================================
export interface INavigationRoute {
name: string
displayName: string
meta: { icon: string }
children?: INavigationRoute[]
}
export default {
root: {
name: '/',
displayName: 'navigationRoutes.home',
},
routes: [
{
name: 'dashboard',
displayName: 'menu.dashboard',
meta: {
icon: 'vuestic-iconset-dashboard',
},
},
{
name: 'users',
displayName: 'menu.users',
meta: {
icon: 'group',
},
},
{
name: 'projects',
displayName: 'menu.projects',
meta: {
icon: 'folder_shared',
},
},
{
name: 'payments',
displayName: 'menu.payments',
meta: {
icon: 'credit_card',
},
children: [
{
name: 'payment-methods',
displayName: 'menu.payment-methods',
},
{
name: 'pricing-plans',
displayName: 'menu.pricing-plans',
},
{
name: 'billing',
displayName: 'menu.billing',
},
],
},
{
name: 'auth',
displayName: 'menu.auth',
meta: {
icon: 'login',
},
children: [
{
name: 'login',
displayName: 'menu.login',
},
{
name: 'signup',
displayName: 'menu.signup',
},
{
name: 'recover-password',
displayName: 'menu.recover-password',
},
],
},
{
name: 'faq',
displayName: 'menu.faq',
meta: {
icon: 'quiz',
},
},
{
name: '404',
displayName: 'menu.404',
meta: {
icon: 'vuestic-iconset-files',
},
},
{
name: 'preferences',
displayName: 'menu.preferences',
meta: {
icon: 'manage_accounts',
},
},
{
name: 'settings',
displayName: 'menu.settings',
meta: {
icon: 'settings',
},
},
] as INavigationRoute[],
}
================================================
FILE: src/components/typography/Typography.stories.ts
================================================
import Typography from './Typography.vue'
export default {
title: 'Typography',
component: Typography,
tags: ['autodocs'],
}
export const Default = () => ({
components: { Typography },
template: `
<Typography/>
`,
})
================================================
FILE: src/components/typography/Typography.vue
================================================
<template>
<VaContent class="typography content">
<div class="grid grid-cols-12 gap-6">
<VaCard class="col-span-12">
<VaCardTitle>Primary text styles</VaCardTitle>
<VaCardContent>
<div class="mb-8">
<h1>Display 1 Heading</h1>
<p>
Of all of the celestial bodies that capture our attention and fascination as astronomers, none has a
greater influence on life on planet Earth than it’s own satellite, the moon. When you think about it.
</p>
</div>
<div class="mb-8">
<h2>Display 2 Heading</h2>
<p>
None has a greater influence on life on planet Earth than it’s own satellite, the moon. When you think
about it.
</p>
</div>
<div class="mb-8">
<h3>Display 3 Heading</h3>
<p>
Let’s talk about meat fondue recipes and what you need to know first. Meat fondue also known as oil fondue
is a method of cooking all kinds of meats, poultry, and seafood in a pot of heated oil.
</p>
</div>
<div class="mb-8">
<h4>Display 4 Heading</h4>
<p>
There is something about parenthood that gives us a sense of history and a deeply rooted desire to send on
into the next generation the great things we have discovered about life.
</p>
</div>
<div class="mb-8">
<h5>Display 5 Heading</h5>
<p>
There is a moment in the life of any aspiring astronomer that it is time to buy that first telescope. It’s
exciting to think about setting up your own viewing station.
</p>
</div>
<div class="mb-8">
<p>
Of all of the celestial bodies that capture our attention and fascination as astronomers, none has a
greater influence on life on planet Earth than it’s own satellite, the moon. When you think about it.
</p>
</div>
<div class="mb-8">
<div class="text--secondary">
Of all of the celestial bodies that capture our attention and fascination as astronomers, none has a
greater influence on life on planet Earth than it’s own satellite, the moon. When you think about it.
</div>
</div>
<div class="mb-8">
<pre class="code-snippet">
<p class=“code-snippet”>
This is a wonderful example.
<a href=“#” onClick=“”>Read more</a>
</p></pre
>
<p>
Of all of the celestial bodies that capture our attention and fascination as astronomers,
<span class="text--code">currentColor</span> none has a greater influence on life on planet Earth than
it’s own satellite, the moon.
</p>
</div>
</VaCardContent>
</VaCard>
<VaCard class="col-span-12">
<VaCardTitle>Secondary text styles</VaCardTitle>
<VaCardContent>
<p class="va-h3">Lists</p>
<ol class="va-ordered">
<li>
Of all of the celestial bodies that capture our attention and fascination as astronomers, none has a
greater influence.
</li>
<li>Earth than it’s own satellite, the moon. When you think about it.</li>
<li>Attention and fascination as.</li>
</ol>
<ol class="va-ordered">
<li>Coffee</li>
<li>
Tea
<ol class="va-ordered">
<li>
Black tea
<ol class="va-ordered">
<li>Brooke Bond</li>
<li>Lipton</li>
</ol>
</li>
<li>
Green tea
<ol class="va-ordered">
<li>Greenfield</li>
<li>Tess</li>
</ol>
</li>
</ol>
</li>
<li>Milk</li>
</ol>
<ul class="va-unordered">
<li>
Of all of the celestial bodies that capture our attention and fascination as astronomers, none has a
greater influence.
</li>
<li>Earth than it’s own satellite, the moon. When you think about it.</li>
<li>Attention and fascination as .</li>
</ul>
<ul class="va-unordered">
<li>Coffee</li>
<li>
Tea
<ul class="va-unordered">
<li>
Black tea
<ul class="va-unordered">
<li>Brooke Bond</li>
<li>Lipton</li>
</ul>
</li>
<li>
Green tea
<ul class="va-unordered">
<li>Greenfield</li>
<li>Tess</li>
</ul>
</li>
</ul>
</li>
<li>Milk</li>
</ul>
<p class="va-h3">Links</p>
<div class="mb-8">
<a class="link mr-8" href="/default" @click.prevent> Default Link </a>
<a class="link-secondary" href="/secondary" @click.prevent> Secondary Link </a>
</div>
<div class="mb-8">
<p class="va-h3">Other Elements</p>
<p>
None has a greater influence on
<span class="text--highlighted">highlighted text</span>
life on planet Earth than it’s own satellite, the selected chunk of text. When you think about it.
</p>
</div>
<div class="mb-8">
<blockquote class="va-blockquote border-primary">
<p>
BQ: Let’s talk about meat fondue recipes and what you need to know first. Meat fondue also known as oil
fondue is a method of cooking all kinds.
</p>
<p>
<i>— Mister Lebowski</i>
</p>
</blockquote>
</div>
<div class="mb-8">
<div class="text-block">
<p class="va-h3">va-h3 Heading</p>
<span
>Of all of the celestial bodies that capture our attention and fascination as astronomers, none has a
greater influence on life on planet Earth than it’s own satellite, the moon. When you think about
it.</span
>
</div>
</div>
<div class="mb-8">
<table class="va-table">
<thead>
<tr>
<th v-for="(data, index) in tableData[0]" :key="index">{{ data }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(rowData, rowIndex) in tableData.slice(1)" :key="rowIndex">
<td v-for="(itemData, colIndex) in rowData" :key="colIndex">
{{ itemData }}
</td>
</tr>
</tbody>
</table>
</div>
</VaCardContent>
</VaCard>
</div>
</VaContent>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
// import { useI18n } from 'vue-i18n'
//
// const { t } = useI18n()
const tableData = computed(() => [
['Id', 'FooBar type', 'Actions'],
['1', 'Zebra', 'Delete'],
['2', 'Not Zebra', 'Remove'],
['3', 'Very Zebra', 'Eradicate'],
])
</script>
<style lang="scss" scoped>
.text--secondary {
color: var(--va-secondary);
}
</style>
================================================
FILE: src/components/va-charts/VaChart.vue
================================================
<template>
<component :is="chartComponent" :chart-data="data" :data="data" :options="chartOptions" class="va-chart" />
</template>
<script lang="ts" setup generic="T extends 'line' | 'bar' | 'bubble' | 'doughnut' | 'pie'">
import { computed } from 'vue'
import type { ChartOptions, ChartData, ChartComponent } from 'chart.js'
import { defaultConfig, chartTypesMap } from './vaChartConfigs'
defineOptions({
name: 'VaChart',
})
const props = defineProps<{
data: ChartData<T>
options?: ChartOptions<T>
type: T
}>()
const chartComponent = chartTypesMap[props.type] as unknown as ChartComponent
const chartOptions = computed<ChartOptions<T>>(() => ({
...(defaultConfig as any),
...props.options,
}))
</script>
<style lang="scss">
.va-chart {
min-width: 100%;
min-height: 100%;
display: flex;
align-items: center;
justify-content: center;
> * {
height: 100%;
width: 100%;
}
canvas {
width: 100%;
height: auto;
min-height: 20px;
}
}
</style>
================================================
FILE: src/components/va-charts/chart-types/BarChart.vue
================================================
<template>
<Bar :data="data" :options="options" />
</template>
<script lang="ts" setup>
import { Bar } from 'vue-chartjs'
import type { ChartOptions } from 'chart.js'
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, LinearScale, CategoryScale } from 'chart.js'
ChartJS.register(Title, Tooltip, Legend, BarElement, LinearScale, CategoryScale)
defineProps<{
data: any
options?: ChartOptions<'bar'>
}>()
</script>
================================================
FILE: src/components/va-charts/chart-types/BubbleChart.vue
================================================
<template>
<Bubble :data="props.data" :options="options" />
</template>
<script lang="ts" setup>
import { Bubble } from 'vue-chartjs'
import type { ChartOptions } from 'chart.js'
import { Chart as ChartJS, Title, Tooltip, Legend, PointElement, LinearScale } from 'chart.js'
import { TBubbleChartData } from '../../../data/types'
ChartJS.register(Title, Tooltip, Legend, PointElement, LinearScale)
const props = defineProps<{
data: TBubbleChartData
options?: ChartOptions<'bubble'>
}>()
</script>
================================================
FILE: src/components/va-charts/chart-types/DoughnutChart.vue
================================================
<template>
<Doughnut :data="props.data" :options="options" />
</template>
<script lang="ts" setup>
import { Doughnut } from 'vue-chartjs'
import type { ChartOptions } from 'chart.js'
import { Chart as ChartJS, Title, Tooltip, Legend, ArcElement, CategoryScale } from 'chart.js'
import { TDoughnutChartData } from '../../../data/types'
ChartJS.register(Title, Tooltip, Legend, ArcElement, CategoryScale)
const props = defineProps<{
data: TDoughnutChartData
options?: ChartOptions<'doughnut'>
}>()
</script>
================================================
FILE: src/components/va-charts/chart-types/HorizontalBarChart.vue
================================================
<template>
<Bar :data="props.data" :options="{ ...options, ...horizontalBarOptions }" />
</template>
<script lang="ts" setup>
import { Bar } from 'vue-chartjs'
import type { ChartOptions } from 'chart.js'
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, LinearScale, CategoryScale } from 'chart.js'
import { TBarChartData } from '../../../data/types'
ChartJS.register(Title, Tooltip, Legend, BarElement, LinearScale, CategoryScale)
const horizontalBarOptions = {
indexAxis: 'y' as 'x' | 'y',
elements: {
bar: {
borderWidth: 1,
},
},
}
const props = defineProps<{
data: TBarChartData
options?: ChartOptions<'bar'>
}>()
</script>
================================================
FILE: src/components/va-charts/chart-types/LineChart.vue
================================================
<template>
<Line ref="chart" :data="computedChartData" :options="options" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { Line } from 'vue-chartjs'
import type { ChartOptions } from 'chart.js'
import {
Chart as ChartJS,
Title,
Tooltip,
Legend,
LineElement,
LinearScale,
PointElement,
CategoryScale,
Filler,
} from 'chart.js'
import { TLineChartData } from '../../../data/types'
import { computed } from 'vue'
import { useColors } from 'vuestic-ui'
ChartJS.register(Title, Tooltip, Legend, LineElement, LinearScale, PointElement, CategoryScale, Filler)
const chart = ref<typeof Line>()
const props = defineProps<{
data: TLineChartData
options?: ChartOptions<'line'>
}>()
const ctx = computed(() => {
if (!chart.value) {
return null
}
return chart.value.chart?.ctx ?? null
})
const { setHSLAColor, getColor } = useColors()
const colors = ['primary', 'success', 'danger', 'warning']
const computedChartData = computed<TLineChartData>(() => {
if (!ctx.value) {
return props.data
}
const makeGradient = (bg: string) => {
const gradient = ctx.value!.createLinearGradient(0, 0, 0, 90)
gradient.addColorStop(0, setHSLAColor(bg, { a: 0.4 }))
gradient.addColorStop(1, setHSLAColor(bg, { a: 0.0 }))
return gradient
}
const datasets = props.data.datasets.map((dataset, index) => {
const color = getColor(colors[index % colors.length])
return {
...dataset,
fill: true,
backgroundColor: makeGradient(color),
borderColor: color,
pointRadius: 0,
borderWidth: 2,
}
})
return { ...props.data, datasets }
})
</script>
================================================
FILE: src/components/va-charts/chart-types/Map.vue
================================================
<template>
<canvas ref="canvas" style="max-width: 100%" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { Chart as ChartJS, Title, Tooltip, Legend, ArcElement, CategoryScale, ChartOptions } from 'chart.js'
import { ChoroplethController, ProjectionScale, ColorScale, GeoFeature } from 'chartjs-chart-geo'
import { watchEffect } from 'vue'
import { ChartData } from 'chart.js'
ChartJS.register(
Title,
Tooltip,
Legend,
ArcElement,
CategoryScale,
ChoroplethController,
ProjectionScale,
ColorScale,
GeoFeature,
)
const canvas = ref<HTMLCanvasElement | null>(null)
function getColor(revenue: number) {
return revenue >= 0.9 ? '#63A6F8' : revenue > 0.4 ? '#8FC0FA' : '#EDF0F1'
}
const props = defineProps<{
options?: ChartOptions<'choropleth'>
data: ChartData<'choropleth', { feature: any; value: number }[], string>
}>()
watchEffect(() => {
if (canvas.value === null) {
return
}
new ChartJS(canvas.value.getContext('2d')!, {
type: 'choropleth',
data: props.data,
options: {
plugins: {
legend: {
display: false,
},
},
scales: {
projection: {
axis: 'x',
projection: 'mercator',
projectionScale: 1.6,
},
color: {
axis: 'x',
quantize: 5,
display: false,
interpolate: getColor,
},
},
animation: false,
},
})
})
</script>
================================================
FILE: src/components/va-charts/chart-types/PieChart.vue
================================================
<template>
<Pie :data="props.data" :options="options" />
</template>
<script lang="ts" setup>
import { Pie } from 'vue-chartjs'
import type { ChartOptions } from 'chart.js'
import { Chart as ChartJS, Title, Tooltip, Legend, ArcElement, CategoryScale } from 'chart.js'
import { TPieChartData } from '../../../data/types'
ChartJS.register(Title, Tooltip, Legend, ArcElement, CategoryScale)
const props = defineProps<{
data: TPieChartData
options?: ChartOptions<'pie'>
}>()
</script>
================================================
FILE: src/components/va-charts/external-tooltip.ts
================================================
import { Chart, TooltipModel } from 'chart.js'
import { computePosition, flip, shift } from '@floating-ui/dom'
const getOrCreateTooltip = (chart: Chart) => {
let tooltipEl = chart.canvas.parentNode?.querySelector('div')
if (!tooltipEl) {
tooltipEl = document.createElement('div')
tooltipEl.style.background = 'rgba(0, 0, 0, 0.7)'
tooltipEl.style.borderRadius = '3px'
tooltipEl.style.color = 'white'
tooltipEl.style.opacity = '1'
tooltipEl.style.pointerEvents = 'none'
tooltipEl.style.position = 'absolute'
// tooltipEl.style.transform = 'translate(-50%, 0)'
tooltipEl.style.left = '0'
tooltipEl.style.top = '0'
tooltipEl.style.transition = 'all .1s ease'
tooltipEl.style.height = 'min-content'
tooltipEl.style.maxWidth = '200px'
tooltipEl.style.zIndex = '9999'
const table = document.createElement('table')
table.style.margin = '0px'
tooltipEl.appendChild(table)
chart.canvas.parentNode?.appendChild(tooltipEl)
}
return tooltipEl
}
export const externalTooltipHandler = (context: { chart: Chart; tooltip: TooltipModel<any> }) => {
// Tooltip Element
const { chart, tooltip } = context
const tooltipEl = getOrCreateTooltip(chart)
// Hide if no tooltip
if (tooltip.opacity === 0) {
tooltipEl.style.opacity = '0'
return
}
// Set Text
if (tooltip.body) {
const titleLines = tooltip.title || []
const bodyLines = tooltip.body.map((b) => b.lines)
const tableHead = document.createElement('thead')
titleLines.forEach((title) => {
const tr = document.createElement('tr')
tr.style.borderWidth = '0'
const th = document.createElement('th')
th.style.borderWidth = '0'
const text = document.createTextNode(title)
th.appendChild(text)
tr.appendChild(th)
tableHead.appendChild(tr)
})
const tableBody = document.createElement('tbody')
bodyLines.forEach((body, i) => {
const colors = tooltip.labelColors[i]
const span = document.createElement('span')
span.style.background = String(colors.backgroundColor)
span.style.borderColor = String(colors.borderColor)
span.style.borderWidth = '2px'
span.style.marginRight = '10px'
span.style.height = '10px'
span.style.width = '10px'
span.style.display = 'inline-block'
const tr = document.createElement('tr')
tr.style.backgroundColor = 'inherit'
tr.style.borderWidth = '0'
const td = document.createElement('td')
td.style.borderWidth = '0'
const text = document.createTextNode(body as any)
td.appendChild(span)
td.appendChild(text)
tr.appendChild(td)
tableBody.appendChild(tr)
})
const tableRoot = tooltipEl.querySelector('table')
// Remove old children
while (tableRoot?.firstChild) {
tableRoot.firstChild.remove()
}
// Add new children
tableRoot?.appendChild(tableHead)
tableRoot?.appendChild(tableBody)
}
// Display, position, and set styles for font
tooltipEl.style.opacity = '1'
tooltipEl.style.padding = tooltip.options.padding + 'px ' + tooltip.options.padding + 'px'
computePosition(chart.canvas.parentNode! as HTMLElement, tooltipEl!, {
placement: 'top',
middleware: [flip(), shift()],
}).then(({ x, y }) => {
Object.assign(tooltipEl!.style, {
left: `${x}px`,
top: `${y}px`,
})
})
}
================================================
FILE: src/components/va-charts/vaChartConfigs.js
================================================
import { defineAsyncComponent, markRaw } from 'vue'
const DEFAULT_FONT_FAMILY = "'Inter', sans-serif"
export const defaultConfig = {
scales: {
x: {
ticks: {
font: {
family: DEFAULT_FONT_FAMILY,
},
},
},
y: {
ticks: {
font: {
family: DEFAULT_FONT_FAMILY,
},
},
},
},
plugins: {
legend: {
position: 'bottom',
labels: {
font: {
color: '#34495e',
family: DEFAULT_FONT_FAMILY,
size: 14,
},
usePointStyle: true,
},
},
tooltip: {
bodyFont: {
size: 14,
family: DEFAULT_FONT_FAMILY,
},
boxPadding: 4,
},
},
datasets: {
line: {
fill: 'origin',
tension: 0.3,
borderColor: 'transparent',
},
bubble: {
borderColor: 'transparent',
},
bar: {
borderColor: 'transparent',
},
},
maintainAspectRatio: false,
animation: true,
}
export const doughnutConfig = {
cutout: '80%',
scales: {
x: {
display: false,
grid: {
display: false, // Disable X-axis grid lines ("net")
},
},
y: {
display: false,
grid: {
display: false, // Disable Y-axis grid lines ("net")
},
ticks: {
display: false, // Hide Y-axis values
},
},
},
plugins: {
legend: {
display: false,
},
},
datasets: {
line: {
fill: 'origin',
tension: 0.3,
borderColor: 'transparent',
},
bubble: {
borderColor: 'transparent',
},
bar: {
borderColor: 'transparent',
},
},
maintainAspectRatio: false,
animation: true,
}
export const chartTypesMap = {
pie: markRaw(defineAsyncComponent(() => import('./chart-types/PieChart.vue'))),
doughnut: markRaw(defineAsyncComponent(() => import('./chart-types/DoughnutChart.vue'))),
bubble: markRaw(defineAsyncComponent(() => import('./chart-types/BubbleChart.vue'))),
line: markRaw(defineAsyncComponent(() => import('./chart-types/LineChart.vue'))),
bar: markRaw(defineAsyncComponent(() => import('./chart-types/BarChart.vue'))),
'horizontal-bar': markRaw(defineAsyncComponent(() => import('./chart-types/HorizontalBarChart.vue'))),
}
================================================
FILE: src/components/va-medium-editor/VaMediumEditor.vue
================================================
<template>
<div ref="editorElement" class="va-medium-editor content">
<slot />
</div>
</template>
<script lang="ts" setup>
import { ref, Ref, onMounted, onBeforeUnmount } from 'vue'
import MediumEditor from 'medium-editor'
const props = withDefaults(
defineProps<{
editorOptions?: {
buttonLabels: string
autoLink: boolean
toolbar: {
buttons: string[]
}
}
}>(),
{
editorOptions: () => ({
buttonLabels: 'fontawesome',
autoLink: true,
toolbar: {
buttons: ['bold', 'italic', 'underline', 'anchor', 'h1', 'h2', 'h3'],
},
}),
},
)
const emit = defineEmits<{
(e: 'initialized', editor: typeof MediumEditor): void
}>()
const editorElement: Ref<null | HTMLElement> = ref(null)
let editor: typeof MediumEditor | null = null
onMounted(() => {
if (!editorElement.value) {
return
}
editor = new MediumEditor(editorElement.value, props.editorOptions)
emit('initialized', editor)
})
onBeforeUnmount(() => {
if (editor) {
editor.destroy()
}
})
</script>
<style lang="scss">
@import 'medium-editor/src/sass/medium-editor';
@import 'variables';
$medium-editor-shadow: var(--va-box-shadow);
$medium-editor-background-color: var(--va-background-primary);
$medium-editor-text-color: var(--va-dark);
$medium-editor-active-background-color: var(--va-primary);
$medium-editor-active-text-color: var(--va-white);
.va-medium-editor {
margin-bottom: var(--va-medium-editor-margin-bottom);
min-width: var(--va-medium-editor-min-width);
max-width: var(--va-medium-editor-max-width);
&:focus {
outline: none;
}
&.content {
i {
font-style: italic;
}
}
}
// isn't a part of the .va-medium-editor, so can't be places inside it
.medium-editor-toolbar,
.medium-editor-toolbar-form,
.medium-editor-toolbar-actions,
.medium-editor-toolbar-anchor-preview {
box-shadow: $medium-editor-shadow;
background-color: $medium-editor-background-color;
border-radius: 1.5rem;
height: 44px;
line-height: 42px;
}
.medium-editor-toolbar-anchor-preview {
a {
padding: 0 2rem;
margin: 0;
line-height: 44px;
}
}
.medium-editor-toolbar {
box-shadow: $medium-editor-shadow;
.medium-editor-toolbar-actions {
overflow: hidden;
height: 44px;
}
.medium-editor-action {
margin: 0;
border: 0;
padding: 0.375rem 1rem;
height: 44px;
background-color: $medium-editor-background-color;
box-shadow: none;
border-radius: 0;
i {
color: $medium-editor-text-color;
}
&.medium-editor-button-active {
background-color: $medium-editor-active-background-color;
color: $medium-editor-active-text-color;
i {
color: $medium-editor-active-text-color;
}
}
}
& > .medium-editor-action:not(:last-child) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: 0;
}
& > .medium-editor-action + .medium-editor-action {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left: 0;
}
}
.medium-editor-toolbar-form {
color: $medium-editor-text-color;
overflow: hidden;
a {
color: $medium-editor-text-color;
transform: translateY(1px);
}
input {
margin-left: 4px !important;
transform: translateY(-2px);
border-radius: 13px;
}
.medium-editor-toolbar-close {
margin-right: 1rem;
}
}
.medium-toolbar-arrow-under::after {
border-color: $medium-editor-background-color transparent transparent transparent;
top: 100%;
}
.medium-toolbar-arrow-over::before {
border-color: transparent transparent var(--va-primary) transparent;
}
.medium-editor-toolbar-anchor-preview {
// @include va-button($btn-padding-y-nrm, $btn-padding-x-nrm, $btn-font-size-nrm, $btn-line-height-nrm, $btn-border-radius-nrm);
.medium-editor-toolbar-anchor-preview {
margin: 0;
}
}
.medium-editor-anchor-preview {
max-width: 50%;
a {
color: $medium-editor-text-color;
text-decoration: none;
}
}
</style>
================================================
FILE: src/components/va-medium-editor/_variables.scss
================================================
:root {
--va-medium-editor-margin-bottom: 2.25rem;
--va-medium-editor-min-width: 6rem;
--va-medium-editor-max-width: 600px;
/* Toolbar */
--va-medium-editor-toolbar-max-width: 90%;
--va-medium-editor-toolbar-box-shadow: none;
}
================================================
FILE: src/components/va-timeline-item.vue
================================================
<template>
<tr class="va-timeline-item">
<td class="va-timeline-item__icon-cell">
<div class="va-timeline-item__icon">
<VaIcon name="schedule" size="22px" color="backgroundBorder" />
</div>
</td>
<td class="va-timeline-item__content-cell">
<div class="va-timeline-item__content">
<slot />
</div>
</td>
<td class="va-timeline-item__date-cell">
<slot name="date">
{{ $props.date }}
</slot>
</td>
</tr>
</template>
<script setup lang="ts">
defineProps({
date: {
type: String,
default: '',
},
})
</script>
<style lang="scss" scoped>
.va-timeline-item {
display: table-row;
&__icon-cell {
vertical-align: top;
height: 1px;
padding-right: 1rem;
}
&__icon {
width: 24px;
position: relative;
display: inline-flex;
justify-content: center;
flex-direction: column;
align-items: center;
height: 100%;
&::after {
content: '';
width: 2px;
height: 100%;
background: var(--va-background-border);
}
}
&__content {
margin-bottom: 1rem;
}
&__content-cell {
width: 100%;
}
&__date-cell {
vertical-align: top;
color: var(--va-secondary);
text-wrap: nowrap;
white-space: nowrap;
padding-left: 0.5rem;
text-align: end;
}
&:last-child {
.va-timeline-item__icon {
&::after {
background: transparent;
}
}
}
}
</style>
================================================
FILE: src/data/CountriesList.ts
================================================
export default [
'Afghanistan',
'Albania',
'Algeria',
'American Samoa',
'Andorra',
'Angola',
'Anguilla',
'Antarctica',
'Antigua and Barbuda',
'Argentina',
'Armenia',
'Aruba',
'Australia',
'Austria',
'Azerbaijan',
'Bahamas',
'Bahrain',
'Bangladesh',
'Barbados',
'Belarus',
'Belgium',
'Belize',
'Benin',
'Bermuda',
'Bhutan',
'Bolivia',
'Bosnia and Herzegowina',
'Botswana',
'Bouvet Island',
'Brazil',
'British Indian Ocean Territory',
'Brunei Darussalam',
'Bulgaria',
'Burkina Faso',
'Burundi',
'Cambodia',
'Cameroon',
'Canada',
'Cape Verde',
'Cayman Islands',
'Central African Republic',
'Chad',
'Chile',
'China',
'Christmas Island',
'Cocos (Keeling) Islands',
'Colombia',
'Comoros',
'Congo',
'Congo, the Democratic Republic of the',
'Cook Islands',
'Costa Rica',
"Cote d'Ivoire",
'Croatia (Hrvatska)',
'Cuba',
'Cyprus',
'Czech Republic',
'Denmark',
'Djibouti',
'Dominica',
'Dominican Republic',
'East Timor',
'Ecuador',
'Egypt',
'El Salvador',
'Equatorial Guinea',
'Eritrea',
'Estonia',
'Ethiopia',
'Falkland Islands (Malvinas)',
'Faroe Islands',
'Fiji',
'Finland',
'France',
'France Metropolitan',
'French Guiana',
'French Polynesia',
'French Southern Territories',
'Gabon',
'Gambia',
'Georgia',
'Germany',
'Ghana',
'Gibraltar',
'Greece',
'Greenland',
'Grenada',
'Guadeloupe',
'Guam',
'Guatemala',
'Guinea',
'Guinea-Bissau',
'Guyana',
'Haiti',
'Heard and Mc Donald Islands',
'Holy See (Vatican City State)',
'Honduras',
'Hong Kong',
'Hungary',
'Iceland',
'India',
'Indonesia',
'Iran (Islamic Republic of)',
'Iraq',
'Ireland',
'Israel',
'Italy',
'Jamaica',
'Japan',
'Jordan',
'Kazakhstan',
'Kenya',
'Kiribati',
"Korea, Democratic People's Republic of",
'Korea, Republic of',
'Kuwait',
'Kyrgyzstan',
"Lao, People's Democratic Republic",
'Latvia',
'Lebanon',
'Lesotho',
'Liberia',
'Libyan Arab Jamahiriya',
'Liechtenstein',
'Lithuania',
'Luxembourg',
'Macau',
'Macedonia, The Former Yugoslav Republic of',
'Madagascar',
'Malawi',
'Malaysia',
'Maldives',
'Mali',
'Malta',
'Marshall Islands',
'Martinique',
'Mauritania',
'Mauritius',
'Mayotte',
'Mexico',
'Micronesia, Federated States of',
'Moldova, Republic of',
'Monaco',
'Mongolia',
'Montserrat',
'Morocco',
'Mozambique',
'Myanmar',
'Namibia',
'Nauru',
'Nepal',
'Netherlands',
'Netherlands Antilles',
'New Caledonia',
'New Zealand',
'Nicaragua',
'Niger',
'Nigeria',
'Niue',
'Norfolk Island',
'Northern Mariana Islands',
'Norway',
'Oman',
'Pakistan',
'Palau',
'Panama',
'Papua New Guinea',
'Paraguay',
'Peru',
'Philippines',
'Pitcairn',
'Poland',
'Portugal',
'Puerto Rico',
'Qatar',
'Reunion',
'Romania',
'Russian Federation',
'Rwanda',
'Saint Kitts and Nevis',
'Saint Lucia',
'Saint Vincent and the Grenadines',
'Samoa',
'San Marino',
'Sao Tome and Principe',
'Saudi Arabia',
'Senegal',
'Serbia',
'Seychelles',
'Sierra Leone',
'Singapore',
'Slovakia (Slovak Republic)',
'Slovenia',
'Solomon Islands',
'Somalia',
'South Africa',
'South Georgia and the South Sandwich Islands',
'Spain',
'Sri Lanka',
'St. Helena',
'St. Pierre and Miquelon',
'Sudan',
'Suriname',
'Svalbard and Jan Mayen Islands',
'Swaziland',
'Sweden',
'Switzerland',
'Syrian Arab Republic',
'Taiwan, Province of China',
'Tajikistan',
'Tanzania, United Republic of',
'United States of America',
'Thailand',
'Togo',
'Tokelau',
'Tonga',
'Trinidad and Tobago',
'Tunisia',
'Turkey',
'Turkmenistan',
'Turks and Caicos Islands',
'Tuvalu',
'Uganda',
'Ukraine',
'United Arab Emirates',
'United Kingdom',
'United States',
'United States Minor Outlying Islands',
'Uruguay',
'Uzbekistan',
'Vanuatu',
'Venezuela',
'Vietnam',
'Virgin Islands (British)',
'Virgin Islands (U.S.)',
'Wallis and Futuna Islands',
'Western Sahara',
'Yemen',
'Yugoslavia',
'Zambia',
'Zimbabwe',
]
================================================
FILE: src/data/charts/barChartData.ts
================================================
import { TBarChartData } from '../types'
export const barChartData: TBarChartData = {
labels: [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
],
datasets: [
{
label: 'Last year',
backgroundColor: 'primary',
data: [50, 20, 12, 39, 10, 40, 39, 80, 40, 20, 12, 11],
},
{
label: 'Current year',
backgroundColor: 'info',
data: [50, 10, 22, 39, 15, 20, 85, 32, 60, 50, 20, 30],
},
],
}
================================================
FILE: src/data/charts/bubbleChartData.ts
================================================
import { TBubbleChartData } from '../types'
export const bubbleChartData: TBubbleChartData = {
datasets: [
{
label: 'USA',
backgroundColor: 'danger',
data: [
{
x: 23,
y: 25,
r: 15,
},
{
x: 40,
y: 10,
r: 10,
},
{
x: 30,
y: 22,
r: 30,
},
{
x: 7,
y: 43,
r: 40,
},
{
x: 23,
y: 27,
r: 12,
},
{
x: 20,
y: 15,
r: 11,
},
{
x: 7,
y: 10,
r: 35,
},
{
x: 10,
y: 20,
r: 40,
},
],
},
{
label: 'Russia',
backgroundColor: 'primary',
data: [
{
x: 0,
y: 30,
r: 15,
},
{
x: 20,
y: 20,
r: 20,
},
{
x: 15,
y: 15,
r: 50,
},
{
x: 31,
y: 46,
r: 30,
},
{
x: 20,
y: 14,
r: 25,
},
{
x: 34,
y: 17,
r: 30,
},
{
x: 44,
y: 44,
r: 10,
},
{
x: 39,
y: 25,
r: 35,
},
],
},
{
label: 'Canada',
backgroundColor: 'warning',
data: [
{
x: 10,
y: 30,
r: 45,
},
{
x: 10,
y: 50,
r: 20,
},
{
x: 5,
y: 5,
r: 30,
},
{
x: 40,
y: 30,
r: 20,
},
{
x: 33,
y: 15,
r: 18,
},
{
x: 40,
y: 20,
r: 40,
},
{
x: 33,
y: 33,
r: 40,
},
],
},
{
label: 'Belarus',
backgroundColor: 'info',
data: [
{
x: 35,
y: 30,
r: 45,
},
{
x: 25,
y: 40,
r: 35,
},
{
x: 5,
y: 5,
r: 30,
},
{
x: 5,
y: 20,
r: 40,
},
{
x: 10,
y: 40,
r: 15,
},
{
x: 3,
y: 10,
r: 10,
},
{
x: 15,
y: 40,
r: 40,
},
{
x: 7,
y: 15,
r: 10,
},
],
},
{
label: 'Ukraine',
backgroundColor: 'success',
data: [
{
x: 25,
y: 10,
r: 40,
},
{
x: 17,
y: 40,
r: 40,
},
{
x: 35,
y: 10,
r: 20,
},
{
x: 3,
y: 40,
r: 10,
},
{
x: 40,
y: 40,
r: 40,
},
{
x: 20,
y: 10,
r: 10,
},
{
x: 10,
y: 27,
r: 35,
},
{
x: 7,
y: 26,
r: 40,
},
],
},
],
}
================================================
FILE: src/data/charts/composables/useChartColors.ts
================================================
import { computed, ref, watch } from 'vue'
import { useColors, useGlobalConfig } from 'vuestic-ui'
type chartColors = string | string[]
export function useChartColors(chartColors = [] as chartColors, alfa = 0.6) {
const { getGlobalConfig } = useGlobalConfig()
const { setHSLAColor, getColor } = useColors()
const generateHSLAColors = (colors: chartColors) =>
typeof colors === 'string'
? setHSLAColor(getColor(colors), { a: alfa })
: colors.map((color) => setHSLAColor(getColor(color), { a: alfa }))
const generateColors = (colors: chartColors) =>
typeof colors === 'string' ? getColor(colors) : colors.map((color) => getColor(color))
const generatedHSLAColors = ref(generateHSLAColors(chartColors))
const generatedColors = ref(generateColors(chartColors))
const theme = computed(() => getGlobalConfig().colors!)
watch(theme, () => {
generatedHSLAColors.value = generateHSLAColors(chartColors)
generatedColors.value = generateColors(chartColors)
})
return {
generateHSLAColors,
generateColors,
generatedColors,
generatedHSLAColors,
}
}
================================================
FILE: src/data/charts/composables/useChartData.ts
================================================
import { computed, ComputedRef } from 'vue'
import { useChartColors } from './useChartColors'
import { TChartData } from '../../types'
export function useChartData<T extends TChartData>(data: T, alfa?: number): ComputedRef<T> {
const datasetsColors = data.datasets.map((dataset) => dataset.backgroundColor as string)
const datasetsThemesColors = datasetsColors.map(
(colors) => useChartColors(colors, alfa)[alfa ? 'generatedHSLAColors' : 'generatedColors'],
)
return computed(() => {
const datasets = data.datasets.map((dataset, idx) => ({
...dataset,
backgroundColor: datasetsThemesColors[idx].value,
}))
return { ...data, datasets } as T
})
}
================================================
FILE: src/data/charts/doughnutChartData.ts
================================================
import { TDoughnutChartData } from '../types'
export const profitBackground = '#154EC1'
export const expensesBackground = '#fff'
export const earningsBackground = '#ECF0F1'
export const doughnutChartData: TDoughnutChartData = {
labels: ['Profit', 'Expenses'],
datasets: [
{
label: 'Yearly Breakdown',
backgroundColor: [profitBackground, earningsBackground],
data: [432, 167],
},
],
}
================================================
FILE: src/data/charts/horizontalBarChartData.ts
================================================
import { TBarChartData } from '../types'
export const horizontalBarChartData: TBarChartData = {
labels: [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
],
datasets: [
{
label: 'Vuestic Satisfaction Score',
backgroundColor: 'primary',
data: [80, 90, 50, 70, 60, 90, 50, 90, 80, 40, 72, 93],
},
{
label: 'Bulma Satisfaction Score',
backgroundColor: 'danger',
data: [20, 30, 20, 40, 50, 40, 15, 60, 30, 20, 42, 53],
},
],
}
================================================
FILE: src/data/charts/index.ts
================================================
export { bubbleChartData } from './bubbleChartData'
export { doughnutChartData } from './doughnutChartData'
export { barChartData } from './barChartData'
export { horizontalBarChartData } from './horizontalBarChartData'
export { lineChartData } from './lineChartData'
export { pieChartData } from './pieChartData'
// TODO: clean up charts data, after dashboard rework
================================================
FILE: src/data/charts/lineChartData.ts
================================================
import { TLineChartData } from '../types'
export const lineChartData: TLineChartData = {
labels: [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
],
datasets: [
{
label: 'Monthly Earnings',
backgroundColor: 'rgba(75,192,192,0.4)',
data: [10, 35, 14, 17, 12, 40, 75, 55, 30, 51, 25, 7], // Random values
},
],
}
================================================
FILE: src/data/charts/pieChartData.ts
================================================
import { TLineChartData } from '../types'
export const pieChartData: TLineChartData = {
labels: ['Africa', 'Asia', 'Europe'],
datasets: [
{
label: 'Population (millions)',
backgroundColor: ['primary', 'warning', 'danger'],
data: [2478, 5267, 734],
},
],
}
================================================
FILE: src/data/charts/revenueChartData.ts
================================================
export const earningsColor = '#49A8FF'
export const expensesColor = '#154EC1'
export const months: string[] = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
export type Revenues = {
month: string
earning: number
expenses: number
}
export const generateRevenues = (months: string[]): Revenues[] => {
return months.map((month: string) => {
const earning = Math.floor(Math.random() * 100000 + 10000)
return {
month,
earning,
expenses: Math.floor(earning * Math.random()),
}
})
}
export const getRevenuePerMonth = (month: string, revenues: Revenues[]): Revenues => {
const revenue = revenues.find((revenue) => revenue.month === month)
return revenue || { month, earning: 0, expenses: 0 }
}
export const formatMoney = (amount: number, currency = 'USD'): string => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency,
}).format(amount)
}
================================================
FILE: src/data/geo.json
================================================
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"featurecla": "Admin-0 country",
"scalerank": 1,
"labelrank": 5,
"sovereignt": "Costa Rica",
"sov_a3": "CRI",
"adm0_dif": 0,
"level": 2,
"type": "Sovereign country",
"tlc": "1",
"admin": "Costa Rica",
"adm0_a3": "CRI",
"geou_dif": 0,
"geounit": "Costa Rica",
"gu_a3": "CRI",
"su_dif": 0,
"subunit": "Costa Rica",
"su_a3": "CRI",
"brk_diff": 0,
"name": "Costa Rica",
"name_long": "Costa Rica",
"brk_a3": "CRI",
"brk_name": "Costa Rica",
"brk_group": null,
"abbrev": "C.R.",
"postal": "CR",
"formal_en": "Republic of Costa Rica",
"formal_fr": null,
"name_ciawf": "Costa Rica",
"note_adm0": null,
"note_brk": null,
"name_sort": "Costa Rica",
"name_alt": null,
"mapcolor7": 3,
"mapcolor8": 2,
"mapcolor9": 4,
"mapcolor13": 2,
"pop_est": 5047561,
"pop_rank": 13,
"pop_year": 2019,
"gdp_md": 61801,
"gdp_year": 2019,
"economy": "5. Emerging region: G20",
"income_grp": "3. Upper middle income",
"fips_10": "CS",
"iso_a2": "CR",
"iso_a2_eh": "CR",
"iso_a3": "CRI",
"iso_a3_eh": "CRI",
"iso_n3": "188",
"iso_n3_eh": "188",
"un_a3": "188",
"wb_a2": "CR",
"wb_a3": "CRI",
"woe_id": 23424791,
"woe_id_eh": 23424791,
"woe_note": "Exact WOE match as country",
"adm0_iso": "CRI",
"adm0_diff": null,
"adm0_tlc": "CRI",
"adm0_a3_us": "CRI",
"adm0_a3_fr": "CRI",
"adm0_a3_ru": "CRI",
"adm0_a3_es": "CRI",
"adm0_a3_cn": "CRI",
"adm0_a3_tw": "CRI",
"adm0_a3_in": "CRI",
"adm0_a3_np": "CRI",
"adm0_a3_pk": "CRI",
"adm0_a3_de": "CRI",
"adm0_a3_gb": "CRI",
"adm0_a3_br": "CRI",
"adm0_a3_il": "CRI",
"adm0_a3_ps": "CRI",
"adm0_a3_sa": "CRI",
"adm0_a3_eg": "CRI",
"adm0_a3_ma": "CRI",
"adm0_a3_pt": "CRI",
"adm0_a3_ar": "CRI",
"adm0_a3_jp": "CRI",
"adm0_a3_ko": "CRI",
"adm0_a3_vn": "CRI",
"adm0_a3_tr": "CRI",
"adm0_a3_id": "CRI",
"adm0_a3_pl": "CRI",
"adm0_a3_gr": "CRI",
"adm0_a3_it": "CRI",
"adm0_a3_nl": "CRI",
"adm0_a3_se": "CRI",
"adm0_a3_bd": "CRI",
"adm0_a3_ua": "CRI",
"adm0_a3_un": -99,
"adm0_a3_wb": -99,
"continent": "North America",
"region_un": "Americas",
"subregion": "Central America",
"region_wb": "Latin America & Caribbean",
"name_len": 10,
"long_len": 10,
"abbrev_len": 4,
"tiny": -99,
"homepart": 1,
"min_zoom": 0,
"min_label": 2.5,
"max_label": 8,
"label_x": -84.077922,
"label_y": 10.0651,
"ne_id": 1159320525,
"wikidataid": "Q800",
"name_ar": "كوستاريكا",
"name_bn": "কোস্টা রিকা",
"name_de": "Costa Rica",
"name_en": "Costa Rica",
"name_es": "Costa Rica",
"name_fa": "کاستاریکا",
"name_fr": "Costa Rica",
"name_el": "Κόστα Ρίκα",
"name_he": "קוסטה ריקה",
"name_hi": "कोस्टा रीका",
"name_hu": "Costa Rica",
"name_id": "Kosta Rika",
"name_it": "Costa Rica",
"name_ja": "コスタリカ",
"name_ko": "코스타리카",
"name_nl": "Costa Rica",
"name_pl": "Kostaryka",
"name_pt": "Costa Rica",
"name_ru": "Коста-Рика",
"name_sv": "Costa Rica",
"name_tr": "Kosta Rika",
"name_uk": "Коста-Рика",
"name_ur": "کوسٹاریکا",
"name_vi": "Costa Rica",
"name_zh": "哥斯达黎加",
"name_zht": "哥斯大黎加",
"fclass_iso": "Admin-0 country",
"tlc_diff": null,
"fclass_tlc": "Admin-0 country",
"fclass_us": null,
"fclass_fr": null,
"fclass_ru": null,
"fclass_es": null,
"fclass_cn": null,
"fclass_tw": null,
"fclass_in": null,
"fclass_np": null,
"fclass_pk": null,
"fclass_de": null,
"fclass_gb": null,
"fclass_br": null,
"fclass_il": null,
"fclass_ps": null,
"fclass_sa": null,
"fclass_eg": null,
"fclass_ma": null,
"fclass_pt": null,
"fclass_ar": null,
"fclass_jp": null,
"fclass_ko": null,
"fclass_vn": null,
"fclass_tr": null,
"fclass_id": null,
"fclass_pl": null,
"fclass_gr": null,
"fclass_it": null,
"fclass_nl": null,
"fclass_se": null,
"fclass_bd": null,
"fclass_ua": null,
"filename": "CRI.geojson"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[-82.54619625520348, 9.566134751824677],
[-82.93289099804358, 9.476812038608173],
[-82.92715491405916, 9.074330145702916],
[-82.71918311230053, 8.925708726431495],
[-82.86865719270477, 8.807266343618522],
[-82.82977067740516, 8.62629547773237],
[-82.91317643912421, 8.42351715741907],
[-82.96578304719736, 8.225027980985985],
[-83.50843726269431, 8.446926581247283],
[-83.71147396516908, 8.656836249216866],
[-83.59631303580665, 8.830443223501419],
[-83.63264156770784, 9.051385809765321],
[-83.90988562695374, 9.29080272057358],
[-84.30340165885636, 9.487354030795714],
[-84.64764421256866, 9.61553742109571],
[-84.71335079622777, 9.908051866083852],
[-84.97566036654133, 10.086723130733006],
[-84.91137488477024, 9.795991522658923],
[-85.11092342806532, 9.55703969974131],
[-85.33948828809227, 9.83454214114866],
[-85.66078650586698, 9.933347479690724],
[-85.79744483106285, 10.134885565629034],
[-85.79170874707843, 10.439337266476613],
[-85.65931372754667, 10.75433095951172],
[-85.94172543002176, 10.895278428587801],
[-85.7125404528073, 11.088444932494824],
[-85.5618519762442, 11.217119248901597],
[-84.90300330273895, 10.952303371621896],
[-84.67306901725627, 11.082657172078143],
[-84.35593075228104, 10.999225572142905],
[-84.19017859570485, 10.793450018756674],
[-83.89505449088595, 10.726839097532446],
[-83.65561174186158, 10.938764146361422],
[-83.40231970898296, 10.395438137244653],
[-83.01567664257517, 9.992982082555557],
[-82.54619625520348, 9.566134751824677]
]
]
}
},
{
"type": "Feature",
"properties": {
"featurecla": "Admin-0 country",
"scalerank": 1,
"labelrank": 5,
"sovereignt": "Nicaragua",
"sov_a3": "NIC",
"adm0_dif": 0,
"level": 2,
"type": "Sovereign country",
"tlc": "1",
"admin": "Nicaragua",
"adm0_a3": "NIC",
"geou_dif": 0,
"geounit": "Nicaragua",
"gu_a3": "NIC",
"su_dif": 0,
"subunit": "Nicaragua",
"su_a3": "NIC",
"brk_diff": 0,
"name": "Nicaragua",
"name_long": "Nicaragua",
"brk_a3": "NIC",
"brk_name": "Nicaragua",
"brk_group": null,
"abbrev": "Nic.",
"postal": "NI",
"formal_en": "Republic of Nicaragua",
"formal_fr": null,
"name_ciawf": "Nicaragua",
"note_adm0": null,
"note_brk": null,
"name_sort": "Nicaragua",
"name_alt": null,
"mapcolor7": 1,
"mapcolor8": 4,
"mapcolor9": 1,
"mapcolor13": 9,
"pop_est": 6545502,
"pop_rank": 13,
"pop_year": 2019,
"gdp_md": 12520,
"gdp_year": 2019,
"economy": "6. Developing region",
"income_grp": "4. Lower middle income",
"fips_10": "NU",
"iso_a2": "NI",
"iso_a2_eh": "NI",
"iso_a3": "NIC",
"iso_a3_eh": "NIC",
"iso_n3": "558",
"iso_n3_eh": "558",
"un_a3": "558",
"wb_a2": "NI",
"wb_a3": "NIC",
"woe_id": 23424915,
"woe_id_eh": 23424915,
"woe_note": "Exact WOE match as country",
"adm0_iso": "NIC",
"adm0_diff": null,
"adm0_tlc": "NIC",
"adm0_a3_us": "NIC",
"adm0_a3_fr": "NIC",
"adm0_a3_ru": "NIC",
"adm0_a3_es": "NIC",
"adm0_a3_cn": "NIC",
"adm0_a3_tw": "NIC",
"adm0_a3_in": "NIC",
"adm0_a3_np": "NIC",
"adm0_a3_pk": "NIC",
"adm0_a3_de": "NIC",
"adm0_a3_gb": "NIC",
"adm0_a3_br": "NIC",
"adm0_a3_il": "NIC",
"adm0_a3_ps": "NIC",
"adm0_a3_sa": "NIC",
"adm0_a3_eg": "NIC",
"adm0_a3_ma": "NIC",
"adm0_a3_pt": "NIC",
"adm0_a3_ar": "NIC",
"adm0_a3_jp": "NIC",
"adm0_a3_ko": "NIC",
"adm0_a3_vn": "NIC",
"adm0_a3_tr": "NIC",
"adm0_a3_id": "NIC",
"adm0_a3_pl": "NIC",
"adm0_a3_gr": "NIC",
"adm0_a3_it": "NIC",
"adm0_a3_nl": "NIC",
"adm0_a3_se": "NIC",
"adm0_a3_bd": "NIC",
"adm0_a3_ua": "NIC",
"adm0_a3_un": -99,
"adm0_a3_wb": -99,
"continent": "North America",
"region_un": "Americas",
"subregion": "Central America",
"region_wb": "Latin America & Caribbean",
"name_len": 9,
"long_len": 9,
"abbrev_len": 4,
"tiny": -99,
"homepart": 1,
"min_zoom": 0,
"min_label": 4,
"max_label": 9,
"label_x": -85.069347,
"label_y": 12.670697,
"ne_id": 1159321091,
"wikidataid": "Q811",
"name_ar": "نيكاراغوا",
"name_bn": "নিকারাগুয়া",
"name_de": "Nicaragua",
"name_en": "Nicaragua",
"name_es": "Nicaragua",
"name_fa": "نیکاراگوئه",
"name_fr": "Nicaragua",
"name_el": "Νικαράγουα",
"name_he": "ניקרגואה",
"name_hi": "निकारागुआ",
"name_hu": "Nicaragua",
"name_id": "Nikaragua",
"name_it": "Nicaragua",
"name_ja": "ニカラグア",
"name_ko": "니카라과",
"name_nl": "Nicaragua",
"name_pl": "Nikaragua",
"name_pt": "Nicarágua",
"name_ru": "Никарагуа",
"name_sv": "Nicaragua",
"name_tr": "Nikaragua",
"name_uk": "Нікарагуа",
"name_ur": "نکاراگوا",
"name_vi": "Nicaragua",
"name_zh": "尼加拉瓜",
"name_zht": "尼加拉瓜",
"fclass_iso": "Admin-0 country",
"tlc_diff": null,
"fclass_tlc": "Admin-0 country",
"fclass_us": null,
"fclass_fr": null,
"fclass_ru": null,
"fclass_es": null,
"fclass_cn": null,
"fclass_tw": null,
"fclass_in": null,
"fclass_np": null,
"fclass_pk": null,
"fclass_de": null,
"fclass_gb": null,
"fclass_br": null,
"fclass_il": null,
"fclass_ps": null,
"fclass_sa": null,
"fclass_eg": null,
"fclass_ma": null,
"fclass_pt": null,
"fclass_ar": null,
"fclass_jp": null,
"fclass_ko": null,
"fclass_vn": null,
"fclass_tr": null,
"fclass_id": null,
"fclass_pl": null,
"fclass_gr": null,
"fclass_it": null,
"fclass_nl": null,
"fclass_se": null,
"fclass_bd": null,
"fclass_ua": null,
"filename": "NIC.geojson"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[-83.65561174186158, 10.938764146361422],
[-83.89505449088595, 10.726839097532446],
[-84.19017859570485, 10.793450018756674],
[-84.35593075228104, 10.999225572142905],
[-84.67306901725627, 11.082657172078143],
[-84.90300330273895, 10.952303371621896],
[-85.5618519762442, 11.217119248901597],
[-85.7125404528073, 11.088444932494824],
[-86.05848832878526, 11.403438625529944],
[-86.52584998243296, 11.806876532432597],
[-86.74599158399633, 12.143961900272487],
[-87.16751624220116, 12.458257961471658],
[-87.66849341505471, 12.909909979702633],
[-87.55746660027562, 13.064551703336065],
[-87.39238623731923, 12.914018256069838],
[-87.31665442579549, 12.984685777228975],
[-87.00576900912758, 13.025794379117158],
[-86.88055701368438, 13.254204209847217],
[-86.7338217841916, 13.263092556201443],
[-86.75508663607971, 13.754845485890913],
[-86.52070817741992, 13.778487453664468],
[-86.31214209668993, 13.77135610600817],
[-86.09626380079061, 14.038187364147234],
[-85.80129472526859, 13.83605499923759],
[-85.69866533073696, 13.960078436738002],
[-85.51441301140028, 14.079011745657908],
[-85.16536454948482, 14.354369615125051],
[-85.14875057650296, 14.560196844943617],
[-85.05278744173694, 14.551541042534723],
[-84.9245006985724, 14.79049286545235],
[-84.82003679069436, 14.81958669683267],
[-84.64958207877964, 14.666805324761867],
[-84.4493359036486, 14.621614284722511],
[-84.22834164095241, 14.74876414637663],
[-83.97572140169359, 14.749435939996488],
[-83.62858496777292, 14.880073960830302],
[-83.48998877636612, 15.016267198135537],
[-83.14721900097413, 14.99582916916411],
[-83.23323442252394, 14.899866034398102],
[-83.2841615465476, 14.6766238468972],
[-83.18212643098728, 14.31070302983845],
[-83.41249996614445, 13.970077826386557],
[-83.51983191601468, 13.567699286345883],
[-83.55220720084554, 13.127054348193086],
[-83.49851538769427, 12.869292303921227],
[-83.47332312695198, 12.419087225794428],
[-83.62610449902292, 12.320850328007566],
[-83.71961300325506, 11.893124497927728],
[-83.65085751009072, 11.62903209070012],
[-83.8554703437504, 11.373311265503787],
[-83.80893571647155, 11.103043524617275],
[-83.65561174186158, 10.938764146361422]
]
]
}
},
{
"type": "Feature",
"properties": {
"featurecla": "Admin-0 country",
"scalerank": 1,
"labelrank": 5,
"sovereignt": "Haiti",
"sov_a3": "HTI",
"adm0_dif": 0,
"level": 2,
"type": "Sovereign country",
"tlc": "1",
"admin": "Haiti",
"adm0_a3": "HTI",
"geou_dif": 0,
"geounit": "Haiti",
"gu_a3": "HTI",
"su_dif": 0,
"subunit": "Haiti",
"su_a3": "HTI",
"brk_diff": 0,
"name": "Haiti",
"name_long": "Haiti",
"brk_a3": "HTI",
"brk_name": "Haiti",
"brk_group": null,
"abbrev": "Haiti",
"postal": "HT",
"formal_en": "Republic of Haiti",
"formal_fr": null,
"name_ciawf": "Haiti",
"note_adm0": null,
"note_brk": null,
"name_sort": "Haiti",
"name_alt": null,
"mapcolor7": 2,
"mapcolor8": 1,
"mapcolor9": 7,
"mapcolor13": 2,
"pop_est": 11263077,
"pop_rank": 14,
"pop_year": 2019,
"gdp_md": 14332,
"gdp_year": 2019,
"economy": "7. Least developed region",
"income_grp": "5. Low income",
"fips_10": "HA",
"iso_a2": "HT",
"iso_a2_eh": "HT",
"iso_a3": "HTI",
"iso_a3_eh": "HTI",
"iso_n3": "332",
"iso_n3_eh": "332",
"un_a3": "332",
"wb_a2": "HT",
"wb_a3": "HTI",
"woe_id": 23424839,
"woe_id_eh": 23424839,
"woe_note": "Exact WOE match as country",
"adm0_iso": "HTI",
"adm0_diff": null,
"adm0_tlc": "HTI",
"adm0_a3_us": "HTI",
"adm0_a3_fr": "HTI",
"adm0_a3_ru": "HTI",
"adm0_a3_es": "HTI",
"adm0_a3_cn": "HTI",
"adm0_a3_tw": "HTI",
"adm0_a3_in": "HTI",
"adm0_a3_np": "HTI",
"adm0_a3_pk": "HTI",
"adm0_a3_de": "HTI",
"adm0_a3_gb": "HTI",
"adm0_a3_br": "HTI",
"adm0_a3_il": "HTI",
"adm0_a3_ps": "HTI",
"adm0_a3_sa": "HTI",
"adm0_a3_eg": "HTI",
"adm0_a3_ma": "HTI",
"adm0_a3_pt": "HTI",
"adm0_a3_ar": "HTI",
"adm0_a3_jp": "HTI",
"adm0_a3_ko": "HTI",
"adm0_a3_vn": "HTI",
"adm0_a3_tr": "HTI",
"adm0_a3_id": "HTI",
"adm0_a3_pl": "HTI",
"adm0_a3_gr": "HTI",
"adm0_a3_it": "HTI",
"adm0_a3_nl": "HTI",
"adm0_a3_se": "HTI",
"adm0_a3_bd": "HTI",
"adm0_a3_ua": "HTI",
"adm0_a3_un": -99,
"adm0_a3_wb": -99,
"continent": "North America",
"region_un": "Americas",
"subregion": "Caribbean",
"region_wb": "Latin America & Caribbean",
"name_len": 5,
"long_len": 5,
"abbrev_len": 5,
"tiny": -99,
"homepart": 1,
"min_zoom": 0,
"min_label": 4,
"max_label": 9,
"label_x": -72.224051,
"label_y": 19.263784,
"ne_id": 1159320839,
"wikidataid": "Q790",
"name_ar": "هايتي",
"name_bn": "হাইতি",
"name_de": "Haiti",
"name_en": "Haiti",
"name_es": "Haití",
"name_fa": "هائیتی",
"name_fr": "Haïti",
"name_el": "Αϊτή",
"name_he": "האיטי",
"name_hi": "हैती",
"name_hu": "Haiti",
"name_id": "Haiti",
"name_it": "Haiti",
"name_ja": "ハイチ",
"name_ko": "아이티",
"name_nl": "Haïti",
"name_pl": "Haiti",
"name_pt": "Haiti",
"name_ru": "Республика Гаити",
"name_sv": "Haiti",
"name_tr": "Haiti",
"name_uk": "Гаїті",
"name_ur": "ہیٹی",
"name_vi": "Haiti",
"name_zh": "海地",
"name_zht": "海地",
"fclass_iso": "Admin-0 country",
"tlc_diff": null,
"fclass_tlc": "Admin-0 country",
"fclass_us": null,
"fclass_fr": null,
"fclass_ru": null,
"fclass_es": null,
"fclass_cn": null,
"fclass_tw": null,
"fclass_in": null,
"fclass_np": null,
"fclass_pk": null,
"fclass_de": null,
"fclass_gb": null,
"fclass_br": null,
"fclass_il": null,
"fclass_ps": null,
"fclass_sa": null,
"fclass_eg": null,
"fclass_ma": null,
"fclass_pt": null,
"fclass_ar": null,
"fclass_jp": null,
"fclass_ko": null,
"fclass_vn": null,
"fclass_tr": null,
"fclass_id": null,
"fclass_pl": null,
"fclass_gr": null,
"fclass_it": null,
"fclass_nl": null,
"fclass_se": null,
"fclass_bd": null,
"fclass_ua": null,
"filename": "HTI.geojson"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[-71.71236141629296, 19.714455878167357],
[-71.62487321642283, 19.169837958243306],
[-71.7013026597825, 18.785416978424053],
[-71.94511206733556, 18.61690013272026],
[-71.68773759630588, 18.31666006110447],
[-71.70830481635805, 18.044997056546094],
[-72.37247616238935, 18.21496084235406],
[-72.84441118029488, 18.145611070218365],
[-73.45455481636503, 18.2179063989947],
[-73.92243323433566, 18.030992743395004],
[-74.45803361682478, 18.342549953682706],
[-74.36992529976713, 18.66490753831941],
[-73.44954220243272, 18.526052964751145],
[-72.69493709989064, 18.445799465401862],
[-72.334881557897, 18.668421535715254],
[-72.79164954292489, 19.10162506761803],
[-72.78410478381028, 19.48359141690341],
[-73.41502234566175, 19.639550889560283],
[-73.18979061551762, 19.915683905511912],
[-72.57967281766362, 19.871500555902358],
[-71.71236141629296, 19.714455878167357]
]
]
}
},
{
"type": "Feature",
"properties": {
"featurecla": "Admin-0 country",
"scalerank": 1,
"labelrank": 5,
"sovereignt": "Dominican Republic",
"sov_a3": "DOM",
"adm0_dif": 0,
"level": 2,
"type": "Sovereign country",
"tlc": "1",
"admin": "Dominican Republic",
"adm0_a3": "DOM",
"geou_dif": 0,
"geounit": "Dominican Republic",
"gu_a3": "DOM",
"su_dif": 0,
"subunit": "Dominican Republic",
"su_a3": "DOM",
"brk_diff": 0,
"name": "Dominican Rep.",
"name_long": "Dominican Republic",
"brk_a3": "DOM",
"brk_name": "Dominican Rep.",
"brk_group": null,
"abbrev": "Dom. Rep.",
"postal": "DO",
"formal_en": "Dominican Republic",
"formal_fr": null,
"name_ciawf": "Dominican Republic",
"note_adm0": null,
"note_brk": null,
"name_sort": "Dominican Republic",
"name_alt": null,
"mapcolor7": 5,
"mapcolor8": 2,
"mapcolor9": 5,
"mapcolor13": 7,
"pop_est": 10738958,
"pop_rank": 14,
"pop_year": 2019,
"gdp_md": 88941,
"gdp_year": 2019,
"economy": "6. Developing region",
"income_grp": "3. Upper middle income",
"fips_10": "DR",
"iso_a2": "DO",
"iso_a2_eh": "DO",
"iso_a3": "DOM",
"iso_a3_eh": "DOM",
"iso_n3": "214",
"iso_n3_eh": "214",
"un_a3": "214",
"wb_a2": "DO",
"wb_a3": "DOM",
"woe_id": 23424800,
"woe_id_eh": 23424800,
"woe_note": "Exact WOE match as country",
"adm0_iso": "DOM",
"adm0_diff": null,
"adm0_tlc": "DOM",
"adm0_a3_us": "DOM",
"adm0_a3_fr": "DOM",
"adm0_a3_ru": "DOM",
"adm0_a3_es": "DOM",
"adm0_a3_cn": "DOM",
"adm0_a3_tw": "DOM",
"adm0_a3_in": "DOM",
"adm0_a3_np": "DOM",
"adm0_a3_pk": "DOM",
"adm0_a3_de": "DOM",
"adm0_a3_gb": "DOM",
"adm0_a3_br": "DOM",
"adm0_a3_il": "DOM",
"adm0_a3_ps": "DOM",
"adm0_a3_sa": "DOM",
"adm0_a3_eg": "DOM",
"adm0_a3_ma": "DOM",
"adm0_a3_pt": "DOM",
"adm0_a3_ar": "DOM",
"adm0_a3_jp": "DOM",
"adm0_a3_ko": "DOM",
"adm0_a3_vn": "DOM",
"adm0_a3_tr": "DOM",
"adm0_a3_id": "DOM",
"adm0_a3_pl": "DOM",
"adm0_a3_gr": "DOM",
"adm0_a3_it": "DOM",
"adm0_a3_nl": "DOM",
"adm0_a3_se": "DOM",
"adm0_a3_bd": "DOM",
"adm0_a3_ua": "DOM",
"adm0_a3_un": -99,
"adm0_a3_wb": -99,
"continent": "North America",
"region_un": "Americas",
"subregion": "Caribbean",
"region_wb": "Latin America & Caribbean",
"name_len": 14,
"long_len": 18,
"abbrev_len": 9,
"tiny": -99,
"homepart": 1,
"min_zoom": 0,
"min_label": 4.5,
"max_label": 9.5,
"label_x": -70.653998,
"label_y": 19.104137,
"ne_id": 1159320563,
"wikidataid": "Q786",
"name_ar": "جمهورية الدومينيكان",
"name_bn": "ডোমিনিকান প্রজাতন্ত্র",
"name_de": "Dominikanische Republik",
"name_en": "Dominican Republic",
"name_es": "República Dominicana",
"name_fa": "جمهوری دومینیکن",
"name_fr": "République dominicaine",
"name_el": "Δομινικανή Δημοκρατία",
"name_he": "הרפובליקה הדומיניקנית",
"name_hi": "डोमिनिकन गणराज्य",
"name_hu": "Dominikai Köztársaság",
"name_id": "Republik Dominika",
"name_it": "Repubblica Dominicana",
"name_ja": "ドミニカ共和国",
"name_ko": "도미니카 공화국",
"name_nl": "Dominicaanse Republiek",
"name_pl": "Dominikana",
"name_pt": "República Dominicana",
"name_ru": "Доминиканская Республика",
"
gitextract_vqblgh86/ ├── .editorconfig ├── .eslintrc.cjs ├── .gitattributes ├── .github/ │ ├── COMMIT_CONVENTION.md │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ └── workflows/ │ └── playwright.yml ├── .gitignore ├── .husky/ │ ├── .gitignore │ └── pre-commit ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .storybook/ │ ├── main.ts │ ├── preview.ts │ └── storybook-main.scss ├── .vscode/ │ └── extensions.json ├── .yarnrc.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.ja-JP.md ├── README.md ├── README.zh-CN.md ├── _redirects ├── docs/ │ └── pre-production.md ├── e2e/ │ ├── .gitignore │ ├── README.MD │ ├── package.json │ ├── playwright.config.ts │ ├── stubs/ │ │ ├── index.ts │ │ ├── projects.ts │ │ └── users.ts │ ├── tests/ │ │ └── vuestic-admin.spec.ts │ └── utils/ │ └── index.ts ├── index.html ├── netlify.toml ├── package.json ├── postcss.config.js ├── public/ │ └── site.webmanifest ├── 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 │ │ │ └── widgets/ │ │ │ ├── Categories.vue │ │ │ ├── Navigation.vue │ │ │ ├── Questions.vue │ │ │ └── RequestDemo.vue │ │ ├── payments/ │ │ │ ├── PaymentsPage.vue │ │ │ ├── payment-system/ │ │ │ │ ├── PaymentSystem.stories.ts │ │ │ │ └── PaymentSystem.vue │ │ │ ├── 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.scss │ │ ├── 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
SYMBOL INDEX (68 symbols across 30 files)
FILE: src/components/sidebar/NavigationRoutes.ts
type INavigationRoute (line 1) | interface INavigationRoute {
FILE: src/components/va-charts/vaChartConfigs.js
constant DEFAULT_FONT_FAMILY (line 3) | const DEFAULT_FONT_FAMILY = "'Inter', sans-serif"
FILE: src/data/charts/composables/useChartColors.ts
type chartColors (line 4) | type chartColors = string | string[]
function useChartColors (line 6) | function useChartColors(chartColors = [] as chartColors, alfa = 0.6) {
FILE: src/data/charts/composables/useChartData.ts
function useChartData (line 5) | function useChartData<T extends TChartData>(data: T, alfa?: number): Com...
FILE: src/data/charts/revenueChartData.ts
type Revenues (line 6) | type Revenues = {
FILE: src/data/pages/projects.ts
type Pagination (line 4) | type Pagination = {
type Sorting (line 10) | type Sorting = {
FILE: src/data/pages/users.ts
type Pagination (line 4) | type Pagination = {
type Sorting (line 10) | type Sorting = {
type Filters (line 15) | type Filters = {
FILE: src/data/types.ts
type ColorThemes (line 3) | type ColorThemes = {
type TLineChartData (line 7) | type TLineChartData = ChartData<'line', any, any>
type TBarChartData (line 8) | type TBarChartData = ChartData<'bar', any, any>
type TBubbleChartData (line 9) | type TBubbleChartData = ChartData<'bubble', any, any>
type TDoughnutChartData (line 10) | type TDoughnutChartData = ChartData<'doughnut', any, any>
type TPieChartData (line 11) | type TPieChartData = ChartData<'pie', any, any>
type TChartData (line 13) | type TChartData = TLineChartData | TBarChartData | TBubbleChartData | TD...
FILE: src/pages/payments/types.ts
type PaymentSystemType (line 1) | enum PaymentSystemType {
type PaymentCard (line 8) | interface PaymentCard {
type BillingAddress (line 17) | interface BillingAddress {
FILE: src/pages/payments/widgets/billing-address/BillingAddressCreateModal.stories.ts
method data (line 12) | data() {
FILE: src/pages/payments/widgets/billing-address/BillingAddressEdit.stories.ts
method data (line 12) | data() {
method data (line 40) | data() {
FILE: src/pages/payments/widgets/billing-address/BillingAddressListItem.stories.ts
method data (line 12) | data() {
FILE: src/pages/payments/widgets/billing-address/BillingAddressUpdateModal.stories.ts
method data (line 13) | data() {
FILE: src/pages/payments/widgets/my-cards/PaymentCardCreateModal.stories.ts
method data (line 12) | data() {
FILE: src/pages/payments/widgets/my-cards/PaymentCardEdit.stories.ts
method data (line 12) | data() {
method data (line 38) | data() {
FILE: src/pages/payments/widgets/my-cards/PaymentCardListItem.stories.ts
method data (line 12) | data() {
FILE: src/pages/payments/widgets/my-cards/PaymentCardUpdateModal.stories.ts
method data (line 13) | data() {
FILE: src/pages/pricing-plans/options.ts
type PricingPlans (line 1) | type PricingPlans = {
type Feature (line 11) | type Feature = {
FILE: src/pages/projects/composables/useProjectUsers.ts
function useProjectUsers (line 4) | function useProjectUsers() {
FILE: src/pages/projects/composables/useProjects.ts
method add (line 77) | async add(project: Omit<Project, 'id' | 'created_at'>) {
method update (line 84) | async update(project: Project) {
method remove (line 91) | async remove(project: Project) {
FILE: src/pages/projects/types.ts
type UUID (line 3) | type UUID = `${string}-${string}-${string}-${string}-${string}`
type Project (line 5) | type Project = {
type EmptyProject (line 14) | type EmptyProject = Omit<Project, 'id' | 'project_owner' | 'created_at' ...
FILE: src/pages/users/composables/useUsers.ts
method add (line 89) | async add(user: User) {
method update (line 100) | async update(user: User) {
method remove (line 111) | async remove(user: User) {
method uploadAvatar (line 122) | async uploadAvatar(avatar: Blob) {
FILE: src/pages/users/types.ts
type UserRole (line 1) | type UserRole = 'admin' | 'user' | 'owner'
type UUID (line 3) | type UUID = `${string}-${string}-${string}-${string}-${string}`
type User (line 5) | type User = {
FILE: src/router/index.ts
method scrollBehavior (line 112) | scrollBehavior(to, from, savedPosition) {
FILE: src/stores/billing-addresses.ts
method load (line 52) | async load() {
method create (line 57) | create(address: BillingAddress) {
method update (line 60) | update(address: BillingAddress) {
method remove (line 66) | remove(addressId: string) {
FILE: src/stores/global-store.ts
method toggleSidebar (line 11) | toggleSidebar() {
FILE: src/stores/payment-cards.ts
method load (line 49) | async load() {
method create (line 54) | create(card: PaymentCard) {
method update (line 57) | update(card: PaymentCard) {
method remove (line 63) | remove(cardId: string) {
FILE: src/stores/projects.ts
method getAll (line 18) | async getAll(options: { pagination: Pagination; sorting?: Sorting }) {
method add (line 27) | async add(project: Omit<Project, 'id' | 'created_at'>) {
method update (line 32) | async update(project: Project) {
method remove (line 38) | async remove(project: Project) {
FILE: src/stores/user-store.ts
method toggle2FA (line 15) | toggle2FA() {
method changeUserName (line 19) | changeUserName(userName: string) {
FILE: src/stores/users.ts
method getAll (line 23) | async getAll(options: { pagination?: Pagination; sorting?: Sorting; filt...
method add (line 33) | async add(user: User) {
method update (line 39) | async update(user: User) {
method remove (line 46) | async remove(user: User) {
method uploadAvatar (line 55) | async uploadAvatar(formData: FormData) {
Condensed preview — 220 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,019K chars).
[
{
"path": ".editorconfig",
"chars": 147,
"preview": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_"
},
{
"path": ".eslintrc.cjs",
"chars": 1278,
"preview": "module.exports = {\n root: true,\n env: {\n browser: true,\n es2021: true,\n node: true,\n 'vue/setup-compiler-m"
},
{
"path": ".gitattributes",
"chars": 59,
"preview": "e2e/__screenshots__/** filter=lfs diff=lfs merge=lfs -text\n"
},
{
"path": ".github/COMMIT_CONVENTION.md",
"chars": 3297,
"preview": "## Git Commit Message Convention\n\n> This is adapted from [Angular's commit convention](https://github.com/conventional-c"
},
{
"path": ".github/CONTRIBUTING.md",
"chars": 3104,
"preview": "# Vue.js Contributing Guide\n\nHi! We are really excited that you are interested in contributing to Vuestic. Before submit"
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 821,
"preview": "**Note: for support questions, please use stackoverflow**. This repository's issues are reserved for feature requests an"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 902,
"preview": "<!--\nMAKE SURE TO READ THE CONTRIBUTING GUIDE BEFORE CREATING A PR\nhttps://github.com/epicmaxco/vuestic-admin/blob/maste"
},
{
"path": ".github/dependabot.yml",
"chars": 97,
"preview": "version: 2\nupdates:\n - package-ecosystem: \"npm\"\n directory: \"/\"\n target-branch: \"develop\"\n"
},
{
"path": ".github/workflows/playwright.yml",
"chars": 3074,
"preview": "name: Playwright Tests\n\non:\n workflow_dispatch:\n inputs:\n target_url:\n description: 'Netlify build URL t"
},
{
"path": ".gitignore",
"chars": 315,
"preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\n.yarn/install-state.g"
},
{
"path": ".husky/.gitignore",
"chars": 2,
"preview": "_\n"
},
{
"path": ".husky/pre-commit",
"chars": 58,
"preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n"
},
{
"path": ".nvmrc",
"chars": 3,
"preview": "20\n"
},
{
"path": ".prettierignore",
"chars": 225,
"preview": "# Ignore everything recursively...\n*\n\n# But not the .{ts,js,html,css,scss,vue,json,md} files\n!*.ts\n!*.js\n!*.html\n!*.css\n"
},
{
"path": ".prettierrc",
"chars": 162,
"preview": "{\n \"tabWidth\": 2,\n \"semi\": false,\n \"singleQuote\": true,\n \"quoteProps\": \"as-needed\",\n \"trailingComma\": \"all\",\n \"bra"
},
{
"path": ".storybook/main.ts",
"chars": 385,
"preview": "import type { StorybookConfig } from '@storybook/vue3-vite'\n\nconst config: StorybookConfig = {\n stories: ['../src/**/*."
},
{
"path": ".storybook/preview.ts",
"chars": 629,
"preview": "import { type Preview, setup } from '@storybook/vue3'\nimport { createVuestic } from 'vuestic-ui'\nimport vuesticGlobalCon"
},
{
"path": ".storybook/storybook-main.scss",
"chars": 249,
"preview": "@import url('https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.min.css');\n@import url('https://fonts.googleapis.com/css"
},
{
"path": ".vscode/extensions.json",
"chars": 39,
"preview": "{\n \"recommendations\": [\"Vue.volar\"]\n}\n"
},
{
"path": ".yarnrc.yml",
"chars": 25,
"preview": "nodeLinker: node-modules\n"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3251,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2024 Epicmax LLC\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "README.ja-JP.md",
"chars": 3649,
"preview": "<p align=\"center\">\n <a href=\"https://vuestic.dev\" target=\"_blank\">\n <img alt=\"Vuestic UI ロゴ\" width=\"220\" src=\"./.git"
},
{
"path": "README.md",
"chars": 5056,
"preview": "<p align=\"center\">\n <a href=\"https://vuestic.dev\" target=\"_blank\">\n <img alt=\"Vuestic UI Logo\" width=\"220\" src=\"./.g"
},
{
"path": "README.zh-CN.md",
"chars": 3068,
"preview": "<p align=\"center\">\n <a href=\"https://vuestic.dev\" target=\"_blank\">\n <img alt=\"Vuestic UI Logo\" width=\"220\" src=\"./.g"
},
{
"path": "_redirects",
"chars": 21,
"preview": "/* /index.html 200\n"
},
{
"path": "docs/pre-production.md",
"chars": 1894,
"preview": "# Pre-production\n\n## SEO\n\nWe have a boierplate prepared with some analytics ingrained. This includes:\n\n- [Yandex Metrica"
},
{
"path": "e2e/.gitignore",
"chars": 77,
"preview": "test-results\nplaywright-report\nblob-report\nplaywright/.cache\n__screenshots__\n"
},
{
"path": "e2e/README.MD",
"chars": 2171,
"preview": "# End-to-End Testing with Playwright\n\nThis project uses **Playwright** for end-to-end (E2E) testing.\nAll test logic live"
},
{
"path": "e2e/package.json",
"chars": 225,
"preview": "{\n \"name\": \"e2e\",\n \"private\": true,\n \"version\": \"0.0.0\",\n \"scripts\": {\n \"test\": \"playwright test\",\n \"update\": "
},
{
"path": "e2e/playwright.config.ts",
"chars": 2603,
"preview": "import { defineConfig, devices } from '@playwright/test'\n\n// Chromium – 3 breakpoints\nconst projectsChromium = [\n {\n "
},
{
"path": "e2e/stubs/index.ts",
"chars": 126,
"preview": "import { getProjectsStub } from './projects'\nimport { getUsersStub } from './users'\n\nexport { getProjectsStub, getUsersS"
},
{
"path": "e2e/stubs/projects.ts",
"chars": 720,
"preview": "export const getProjectsStub = () => [\n {\n id: 'bd25e86b-4330-4c1e-bb53-0cea27a04a0f',\n project_name: 'nxn',\n "
},
{
"path": "e2e/stubs/users.ts",
"chars": 20264,
"preview": "export const getUsersStub = () => [\n {\n id: '120feece-1484-417f-80cc-9012bbf67753',\n email: 'dddaaa@email.com',\n "
},
{
"path": "e2e/tests/vuestic-admin.spec.ts",
"chars": 1485,
"preview": "import { test } from '@playwright/test'\n\nimport { testApiMocksPage } from '../utils'\n\nimport { getProjectsStub, getUsers"
},
{
"path": "e2e/utils/index.ts",
"chars": 682,
"preview": "import { expect } from '@playwright/test'\nimport type { Page } from 'playwright-core'\n\nexport const testApiMocksPage = a"
},
{
"path": "index.html",
"chars": 837,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "netlify.toml",
"chars": 136,
"preview": "[build]\npublish = \"dist\"\n\n# Default build command.\ncommand = \"yarn build:ci\"\n\n[[redirects]]\nfrom = \"/*\"\nto = \"/index.htm"
},
{
"path": "package.json",
"chars": 2471,
"preview": "{\n \"name\": \"vuestic-admin\",\n \"private\": true,\n \"version\": \"3.1.0\",\n \"type\": \"module\",\n \"workspaces\": [\n \"e2e\"\n "
},
{
"path": "postcss.config.js",
"chars": 80,
"preview": "export default {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n}\n"
},
{
"path": "public/site.webmanifest",
"chars": 263,
"preview": "{\"name\":\"\",\"short_name\":\"\",\"icons\":[{\"src\":\"/android-chrome-192x192.png\",\"sizes\":\"192x192\",\"type\":\"image/png\"},{\"src\":\"/"
},
{
"path": "src/App.vue",
"chars": 261,
"preview": "<template>\n <RouterView />\n</template>\n\n<style lang=\"scss\">\n#app {\n font-family: 'Inter', Avenir, Helvetica, Arial, sa"
},
{
"path": "src/components/NotFoundImage.vue",
"chars": 5982,
"preview": "<template>\n <svg fill=\"none\" height=\"200\" viewBox=\"0 0 200 200\" width=\"200\" xmlns=\"http://www.w3.org/2000/svg\">\n <pa"
},
{
"path": "src/components/VuesticLogo.stories.ts",
"chars": 659,
"preview": "import VuesticLogo from './VuesticLogo.vue'\n\nexport default {\n title: 'VuesticLogo',\n component: VuesticLogo,\n tags: "
},
{
"path": "src/components/VuesticLogo.vue",
"chars": 5334,
"preview": "<template>\n <svg :height=\"height\" alt=\"Vuestic Admin\" fill=\"none\" viewBox=\"0 0 478 57\" xmlns=\"http://www.w3.org/2000/sv"
},
{
"path": "src/components/app-layout-navigation/AppLayoutNavigation.vue",
"chars": 2357,
"preview": "<template>\n <div class=\"flex gap-2\">\n <VaIconMenuCollapsed\n class=\"cursor-pointer\"\n :class=\"{ 'x-flip': !i"
},
{
"path": "src/components/icons/VaIconCleanCode.vue",
"chars": 1315,
"preview": "<template>\n <svg class=\"va-icon-clean-code\" viewBox=\"0 0 56.02 50.34\" xmlns=\"http://www.w3.org/2000/svg\">\n <defs />\n"
},
{
"path": "src/components/icons/VaIconColor.vue",
"chars": 977,
"preview": "<template>\n <svg\n :fill=\"color\"\n class=\"va-icon-color\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n width=\"24\"\n "
},
{
"path": "src/components/icons/VaIconDiscord.vue",
"chars": 3183,
"preview": "<template>\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\">\n <g clip"
},
{
"path": "src/components/icons/VaIconFaster.vue",
"chars": 1100,
"preview": "<template>\n <svg class=\"va-icon-faster\" version=\"1.1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <!-- "
},
{
"path": "src/components/icons/VaIconFree.vue",
"chars": 1671,
"preview": "<template>\n <svg class=\"va-icon-free\" viewBox=\"0 0 44.99 51.04\" xmlns=\"http://www.w3.org/2000/svg\">\n <defs />\n <t"
},
{
"path": "src/components/icons/VaIconFresh.vue",
"chars": 1390,
"preview": "<template>\n <svg class=\"va-icon-fresh\" viewBox=\"0 0 50.98 47.66\" xmlns=\"http://www.w3.org/2000/svg\">\n <defs />\n <"
},
{
"path": "src/components/icons/VaIconGitHub.vue",
"chars": 1028,
"preview": "<template>\n <svg viewBox=\"0 0 98 96\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n fill-rule=\"evenodd\"\n clip"
},
{
"path": "src/components/icons/VaIconHideSidebar.vue",
"chars": 1939,
"preview": "<template>\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\">\n <path\n "
},
{
"path": "src/components/icons/VaIconMenu.vue",
"chars": 799,
"preview": "<template>\n <svg class=\"va-icon-menu\" height=\"18\" viewBox=\"0 0 24 18\" width=\"23\" xmlns=\"http://www.w3.org/2000/svg\">\n "
},
{
"path": "src/components/icons/VaIconMenuCollapsed.vue",
"chars": 825,
"preview": "<template>\n <svg class=\"va-icon-menu-collapsed\" height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/200"
},
{
"path": "src/components/icons/VaIconMessage.vue",
"chars": 532,
"preview": "<template>\n <svg :fill=\"color\" height=\"16\" viewBox=\"0 0 20 16\" width=\"20\" xmlns=\"http://www.w3.org/2000/svg\">\n <path"
},
{
"path": "src/components/icons/VaIconNotification.vue",
"chars": 625,
"preview": "<template>\n <svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n :fill=\"color\"\n d=\"M10 20c"
},
{
"path": "src/components/icons/VaIconResponsive.vue",
"chars": 743,
"preview": "<template>\n <svg class=\"va-icon-responsive\" viewBox=\"0 0 47.5 49\" xmlns=\"http://www.w3.org/2000/svg\">\n <defs />\n "
},
{
"path": "src/components/icons/VaIconRich.vue",
"chars": 1112,
"preview": "<template>\n <svg class=\"va-icon-rich\" viewBox=\"0 0 56.99 55\" xmlns=\"http://www.w3.org/2000/svg\">\n <defs />\n <titl"
},
{
"path": "src/components/icons/VaIconSlower.vue",
"chars": 1103,
"preview": "<template>\n <svg class=\"va-icon-slower\" version=\"1.1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <!-- "
},
{
"path": "src/components/icons/VaIconVue.vue",
"chars": 804,
"preview": "<template>\n <svg class=\"va-icon-vue\" viewBox=\"0 0 55.05 47.8\" xmlns=\"http://www.w3.org/2000/svg\">\n <defs />\n <tit"
},
{
"path": "src/components/icons/VaIconVuestic.vue",
"chars": 4522,
"preview": "<template>\n <svg class=\"va-icon-vuestic\" height=\"31\" viewBox=\"0 0 304 31\" width=\"304\" xmlns=\"http://www.w3.org/2000/svg"
},
{
"path": "src/components/navbar/AppNavbar.vue",
"chars": 1668,
"preview": "<template>\n <VaNavbar class=\"app-layout-navbar py-2 px-0\">\n <template #left>\n <div class=\"left\">\n <Trans"
},
{
"path": "src/components/navbar/components/AppNavbarActions.vue",
"chars": 2362,
"preview": "<template>\n <div class=\"app-navbar-actions\">\n <VaButton\n v-if=\"!isMobile\"\n preset=\"secondary\"\n href=\""
},
{
"path": "src/components/navbar/components/GitHubButton.vue",
"chars": 337,
"preview": "<template>\n <VaButton\n preset=\"secondary\"\n color=\"textPrimary\"\n href=\"https://github.com/epicmaxco/vuestic-adm"
},
{
"path": "src/components/navbar/components/dropdowns/NotificationDropdown.vue",
"chars": 5307,
"preview": "<template>\n <VaDropdown :offset=\"[13, 0]\" class=\"notification-dropdown\" stick-to-edges :close-on-content-click=\"false\">"
},
{
"path": "src/components/navbar/components/dropdowns/ProfileDropdown.vue",
"chars": 3403,
"preview": "<template>\n <div class=\"profile-dropdown-wrapper\">\n <VaDropdown v-model=\"isShown\" :offset=\"[9, 0]\" class=\"profile-dr"
},
{
"path": "src/components/sidebar/AppSidebar.vue",
"chars": 4252,
"preview": "<template>\n <VaSidebar v-model=\"writableVisible\" :width=\"sidebarWidth\" :color=\"color\" minimized-width=\"0\">\n <VaAccor"
},
{
"path": "src/components/sidebar/NavigationRoutes.ts",
"chars": 1987,
"preview": "export interface INavigationRoute {\n name: string\n displayName: string\n meta: { icon: string }\n children?: INavigati"
},
{
"path": "src/components/typography/Typography.stories.ts",
"chars": 235,
"preview": "import Typography from './Typography.vue'\n\nexport default {\n title: 'Typography',\n component: Typography,\n tags: ['au"
},
{
"path": "src/components/typography/Typography.vue",
"chars": 7734,
"preview": "<template>\n <VaContent class=\"typography content\">\n <div class=\"grid grid-cols-12 gap-6\">\n <VaCard class=\"col-s"
},
{
"path": "src/components/va-charts/VaChart.vue",
"chars": 995,
"preview": "<template>\n <component :is=\"chartComponent\" :chart-data=\"data\" :data=\"data\" :options=\"chartOptions\" class=\"va-chart\" />"
},
{
"path": "src/components/va-charts/chart-types/BarChart.vue",
"chars": 434,
"preview": "<template>\n <Bar :data=\"data\" :options=\"options\" />\n</template>\n\n<script lang=\"ts\" setup>\nimport { Bar } from 'vue-char"
},
{
"path": "src/components/va-charts/chart-types/BubbleChart.vue",
"chars": 505,
"preview": "<template>\n <Bubble :data=\"props.data\" :options=\"options\" />\n</template>\n\n<script lang=\"ts\" setup>\nimport { Bubble } fr"
},
{
"path": "src/components/va-charts/chart-types/DoughnutChart.vue",
"chars": 515,
"preview": "<template>\n <Doughnut :data=\"props.data\" :options=\"options\" />\n</template>\n\n<script lang=\"ts\" setup>\nimport { Doughnut "
},
{
"path": "src/components/va-charts/chart-types/HorizontalBarChart.vue",
"chars": 672,
"preview": "<template>\n <Bar :data=\"props.data\" :options=\"{ ...options, ...horizontalBarOptions }\" />\n</template>\n\n<script lang=\"ts"
},
{
"path": "src/components/va-charts/chart-types/LineChart.vue",
"chars": 1655,
"preview": "<template>\n <Line ref=\"chart\" :data=\"computedChartData\" :options=\"options\" />\n</template>\n\n<script lang=\"ts\" setup>\nimp"
},
{
"path": "src/components/va-charts/chart-types/Map.vue",
"chars": 1455,
"preview": "<template>\n <canvas ref=\"canvas\" style=\"max-width: 100%\" />\n</template>\n\n<script lang=\"ts\" setup>\nimport { ref } from '"
},
{
"path": "src/components/va-charts/chart-types/PieChart.vue",
"chars": 490,
"preview": "<template>\n <Pie :data=\"props.data\" :options=\"options\" />\n</template>\n\n<script lang=\"ts\" setup>\nimport { Pie } from 'vu"
},
{
"path": "src/components/va-charts/external-tooltip.ts",
"chars": 3414,
"preview": "import { Chart, TooltipModel } from 'chart.js'\nimport { computePosition, flip, shift } from '@floating-ui/dom'\n\nconst ge"
},
{
"path": "src/components/va-charts/vaChartConfigs.js",
"chars": 2275,
"preview": "import { defineAsyncComponent, markRaw } from 'vue'\n\nconst DEFAULT_FONT_FAMILY = \"'Inter', sans-serif\"\n\nexport const def"
},
{
"path": "src/components/va-medium-editor/VaMediumEditor.vue",
"chars": 4021,
"preview": "<template>\n <div ref=\"editorElement\" class=\"va-medium-editor content\">\n <slot />\n </div>\n</template>\n\n<script lang="
},
{
"path": "src/components/va-medium-editor/_variables.scss",
"chars": 241,
"preview": ":root {\n --va-medium-editor-margin-bottom: 2.25rem;\n --va-medium-editor-min-width: 6rem;\n --va-medium-editor-max-widt"
},
{
"path": "src/components/va-timeline-item.vue",
"chars": 1460,
"preview": "<template>\n <tr class=\"va-timeline-item\">\n <td class=\"va-timeline-item__icon-cell\">\n <div class=\"va-timeline-it"
},
{
"path": "src/data/CountriesList.ts",
"chars": 4155,
"preview": "export default [\n 'Afghanistan',\n 'Albania',\n 'Algeria',\n 'American Samoa',\n 'Andorra',\n 'Angola',\n 'Anguilla',\n "
},
{
"path": "src/data/charts/barChartData.ts",
"chars": 565,
"preview": "import { TBarChartData } from '../types'\n\nexport const barChartData: TBarChartData = {\n labels: [\n 'January',\n 'F"
},
{
"path": "src/data/charts/bubbleChartData.ts",
"chars": 3373,
"preview": "import { TBubbleChartData } from '../types'\n\nexport const bubbleChartData: TBubbleChartData = {\n datasets: [\n {\n "
},
{
"path": "src/data/charts/composables/useChartColors.ts",
"chars": 1110,
"preview": "import { computed, ref, watch } from 'vue'\nimport { useColors, useGlobalConfig } from 'vuestic-ui'\n\ntype chartColors = s"
},
{
"path": "src/data/charts/composables/useChartData.ts",
"chars": 687,
"preview": "import { computed, ComputedRef } from 'vue'\nimport { useChartColors } from './useChartColors'\nimport { TChartData } from"
},
{
"path": "src/data/charts/doughnutChartData.ts",
"chars": 418,
"preview": "import { TDoughnutChartData } from '../types'\n\nexport const profitBackground = '#154EC1'\nexport const expensesBackground"
},
{
"path": "src/data/charts/horizontalBarChartData.ts",
"chars": 606,
"preview": "import { TBarChartData } from '../types'\n\nexport const horizontalBarChartData: TBarChartData = {\n labels: [\n 'Januar"
},
{
"path": "src/data/charts/index.ts",
"chars": 369,
"preview": "export { bubbleChartData } from './bubbleChartData'\nexport { doughnutChartData } from './doughnutChartData'\nexport { bar"
},
{
"path": "src/data/charts/lineChartData.ts",
"chars": 469,
"preview": "import { TLineChartData } from '../types'\n\nexport const lineChartData: TLineChartData = {\n labels: [\n 'January',\n "
},
{
"path": "src/data/charts/pieChartData.ts",
"chars": 289,
"preview": "import { TLineChartData } from '../types'\n\nexport const pieChartData: TLineChartData = {\n labels: ['Africa', 'Asia', 'E"
},
{
"path": "src/data/charts/revenueChartData.ts",
"chars": 949,
"preview": "export const earningsColor = '#49A8FF'\nexport const expensesColor = '#154EC1'\n\nexport const months: string[] = ['Jan', '"
},
{
"path": "src/data/geo.json",
"chars": 1445791,
"preview": "{\n \"type\": \"FeatureCollection\",\n \"features\": [\n {\n \"type\": \"Feature\",\n \"properties\": {\n \"featurecl"
},
{
"path": "src/data/pages/projects-db.json",
"chars": 3828,
"preview": "[\n {\n \"id\": 0,\n \"project_name\": \"Vuestic\",\n \"project_owner\": 13,\n \"team\": [13, 5, 28, 14, 17, 28, 23, 11, 1"
},
{
"path": "src/data/pages/projects.ts",
"chars": 1298,
"preview": "import api from '../../services/api'\nimport { Project } from '../../pages/projects/types'\n\nexport type Pagination = {\n "
},
{
"path": "src/data/pages/users-db.json",
"chars": 7801,
"preview": "[\n {\n \"id\": 1,\n \"fullname\": \"Patrik Radkow\",\n \"email\": \"magicpan@example.gg\",\n \"username\": \"magicpan\",\n "
},
{
"path": "src/data/pages/users.ts",
"chars": 1885,
"preview": "import { User } from '../../pages/users/types'\nimport api from '../../services/api'\n\nexport type Pagination = {\n page: "
},
{
"path": "src/data/types.ts",
"chars": 504,
"preview": "import type { ChartData } from 'chart.js'\n\nexport type ColorThemes = {\n [key: string]: string\n}\n\nexport type TLineChart"
},
{
"path": "src/data/users.json",
"chars": 10214,
"preview": "[\n {\n \"id\": \"5d2c865e9a0bae79a6ef7cfa\",\n \"firstName\": \"Ashley\",\n \"lastName\": \"Mcdaniel\",\n \"fullName\": \"Ashl"
},
{
"path": "src/env.d.ts",
"chars": 73,
"preview": "/// <reference types=\"vite/client\" />\n/// <reference types=\".vuestic\" />\n"
},
{
"path": "src/i18n/index.ts",
"chars": 788,
"preview": "import { createI18n } from 'vue-i18n'\n\nconst fileNameToLocaleModuleDict = import.meta.glob<{ default: Record<string, str"
},
{
"path": "src/i18n/locales/br.json",
"chars": 7296,
"preview": "{\n \"auth\": {\n \"agree\": \"Eu aceito.\",\n \"createAccount\": \"Criar conta\",\n \"createNewAccount\": \"Criar uma nova con"
},
{
"path": "src/i18n/locales/cn.json",
"chars": 6070,
"preview": "{\n \"auth\": {\n \"agree\": \"我同意\",\n \"createAccount\": \"创建账号\",\n \"createNewAccount\": \"创建新账号\",\n \"email\": \"电子邮箱\",\n "
},
{
"path": "src/i18n/locales/es.json",
"chars": 7235,
"preview": "{\n \"auth\": {\n \"agree\": \"Acepto\",\n \"createAccount\": \"Crear cuenta\",\n \"createNewAccount\": \"Crear cuenta nueva\",\n"
},
{
"path": "src/i18n/locales/gb.json",
"chars": 7758,
"preview": "{\n \"auth\": {\n \"agree\": \"I agree to\",\n \"createAccount\": \"Create account\",\n \"createNewAccount\": \"Create New Acco"
},
{
"path": "src/i18n/locales/ir.json",
"chars": 8462,
"preview": "{\n \"auth\": {\n \"agree\": \"با شرایط استفاده موافقم.\",\n \"createAccount\": \"ساخت حساب کاربری\",\n \"createNewAccount\": "
},
{
"path": "src/layouts/AppLayout.vue",
"chars": 2625,
"preview": "<template>\n <VaLayout\n :top=\"{ fixed: true, order: 2 }\"\n :left=\"{ fixed: true, absolute: breakpoints.mdDown, orde"
},
{
"path": "src/layouts/AuthLayout.vue",
"chars": 1262,
"preview": "<template>\n <VaLayout v-if=\"breakpoint.lgUp\" class=\"h-screen bg-[var(--va-background-secondary)]\">\n <template #left>"
},
{
"path": "src/layouts/RouterBypass.vue",
"chars": 96,
"preview": "<template>\n <div class=\"max-w-7xl mx-auto\">\n <RouterView></RouterView>\n </div>\n</template>\n"
},
{
"path": "src/main.ts",
"chars": 661,
"preview": "import './scss/main.scss'\n\nimport { createApp } from 'vue'\nimport App from './App.vue'\nimport i18n from './i18n'\nimport "
},
{
"path": "src/pages/404.vue",
"chars": 1013,
"preview": "<script lang=\"ts\" setup>\nimport VuesticLogo from '../components/VuesticLogo.vue'\nimport NotFoundImage from '../component"
},
{
"path": "src/pages/admin/dashboard/Dashboard.vue",
"chars": 1178,
"preview": "<script lang=\"ts\" setup>\nimport RevenueUpdates from './cards/RevenueReport.vue'\nimport ProjectTable from './cards/Projec"
},
{
"path": "src/pages/admin/dashboard/DataSection.vue",
"chars": 1923,
"preview": "<template>\n <div class=\"grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4\">\n <DataSectionItem\n v-for=\"metric"
},
{
"path": "src/pages/admin/dashboard/DataSectionItem.vue",
"chars": 1195,
"preview": "<template>\n <VaCard>\n <VaCardContent>\n <section>\n <header class=\"flex items-center justify-between\">\n "
},
{
"path": "src/pages/admin/dashboard/cards/MonthlyEarnings.vue",
"chars": 1682,
"preview": "<template>\n <VaCard>\n <VaCardTitle>\n <h1 class=\"card-title text-tag text-secondary font-bold uppercase\">Monthly"
},
{
"path": "src/pages/admin/dashboard/cards/ProjectTable.vue",
"chars": 2559,
"preview": "<script setup lang=\"ts\">\nimport { defineVaDataTableColumns } from 'vuestic-ui'\nimport UserAvatar from '../../../users/wi"
},
{
"path": "src/pages/admin/dashboard/cards/RegionRevenue.vue",
"chars": 2261,
"preview": "<template>\n <VaCard>\n <VaCardTitle class=\"flex justify-between\">\n <h1 class=\"card-title text-secondary font-bol"
},
{
"path": "src/pages/admin/dashboard/cards/RevenueByLocationMap.vue",
"chars": 1897,
"preview": "<template>\n <VaCard class=\"flex flex-col\">\n <VaCardTitle class=\"flex items-center justify-between\">\n <h1 class="
},
{
"path": "src/pages/admin/dashboard/cards/RevenueReport.vue",
"chars": 2850,
"preview": "<template>\n <VaCard class=\"flex flex-col\">\n <VaCardTitle class=\"flex items-start justify-between\">\n <h1 class=\""
},
{
"path": "src/pages/admin/dashboard/cards/RevenueReportChart.vue",
"chars": 2647,
"preview": "<template>\n <div class=\"flex justify-center w-full h-full overflow-hidden relative\">\n <canvas ref=\"canvas\" style=\"ma"
},
{
"path": "src/pages/admin/dashboard/cards/Timeline.vue",
"chars": 2022,
"preview": "<script setup lang=\"ts\">\nimport VaTimelineItem from '../../../../components/va-timeline-item.vue'\n</script>\n\n<template>\n"
},
{
"path": "src/pages/admin/dashboard/cards/YearlyBreakup.vue",
"chars": 2313,
"preview": "<template>\n <VaCard>\n <VaCardTitle class=\"pb-0!\">\n <h1 class=\"card-title text-secondary font-bold uppercase\">Ye"
},
{
"path": "src/pages/admin/pages/404PagesPage.vue",
"chars": 1160,
"preview": "<template>\n <div class=\"grid grid-cols-12 gap-6\">\n <VaCard\n v-for=\"(item, index) in items\"\n :key=\"index\"\n "
},
{
"path": "src/pages/auth/CheckTheEmail.vue",
"chars": 512,
"preview": "<template>\n <div>\n <h1 class=\"font-semibold text-4xl mb-4\">Check the email</h1>\n <p class=\"text-base mb-4 leading"
},
{
"path": "src/pages/auth/Login.vue",
"chars": 2141,
"preview": "<template>\n <VaForm ref=\"form\" @submit.prevent=\"submit\">\n <h1 class=\"font-semibold text-4xl mb-4\">Log in</h1>\n <p"
},
{
"path": "src/pages/auth/RecoverPassword.vue",
"chars": 1103,
"preview": "<template>\n <VaForm ref=\"passwordForm\" @submit.prevent=\"submit\">\n <h1 class=\"font-semibold text-4xl mb-4\">Forgot you"
},
{
"path": "src/pages/auth/Signup.vue",
"chars": 3049,
"preview": "<template>\n <VaForm ref=\"form\" @submit.prevent=\"submit\">\n <h1 class=\"font-semibold text-4xl mb-4\">Sign up</h1>\n <"
},
{
"path": "src/pages/billing/BillingPage.vue",
"chars": 699,
"preview": "<template>\n <h1 class=\"h1\">Billing information</h1>\n\n <VaSkeletonGroup v-if=\"cardStore.loading\">\n <VaSkeleton class"
},
{
"path": "src/pages/billing/Invoices.vue",
"chars": 2751,
"preview": "<template>\n <VaCard class=\"mb-6\">\n <VaCardContent>\n <h2 class=\"page-sub-title\">Invoices</h2>\n <template v-"
},
{
"path": "src/pages/billing/MembeshipTier.vue",
"chars": 3091,
"preview": "<template>\n <VaCard class=\"mb-6\">\n <VaCardContent>\n <h2 class=\"page-sub-title\">Membership tier</h2>\n <temp"
},
{
"path": "src/pages/billing/PaymentInfo.vue",
"chars": 2993,
"preview": "<template>\n <VaCard class=\"mb-6\">\n <VaCardContent>\n <h2 class=\"page-sub-title\">Payment info</h2>\n\n <div cl"
},
{
"path": "src/pages/billing/modals/ChangeYourPaymentPlan.vue",
"chars": 1145,
"preview": "<template>\n <VaModal\n :mobile-fullscreen=\"false\"\n size=\"small\"\n max-width=\"380px\"\n hide-default-actions\n "
},
{
"path": "src/pages/billing/types.ts",
"chars": 0,
"preview": ""
},
{
"path": "src/pages/faq/FaqPage.vue",
"chars": 368,
"preview": "<template>\n <h1 class=\"page-title\">How can we help you?</h1>\n <Categories />\n <RequestDemo />\n <Questions />\n <Navi"
},
{
"path": "src/pages/faq/data/navigationLinks.json",
"chars": 1668,
"preview": "{\n \"Rising cost of living\": [\n {\n \"name\": \"Fraud and Security\"\n },\n {\n \"name\": \"Secure Key help\"\n "
},
{
"path": "src/pages/faq/data/popularCategories.json",
"chars": 1430,
"preview": "[\n {\n \"id\": 1,\n \"icon\": \"diversity_1\",\n \"name\": \"Getting Started\",\n \"intro\": \"Start using Service easily wi"
},
{
"path": "src/pages/faq/widgets/Categories.vue",
"chars": 1472,
"preview": "<template>\n <VaInput v-model=\"searchValue\" class=\"mb-4\" placeholder=\"Search\">\n <template #appendInner>\n <VaIcon"
},
{
"path": "src/pages/faq/widgets/Navigation.vue",
"chars": 695,
"preview": "<template>\n <VaCard class=\"mb-4 p-6\">\n <section class=\"grid sm:grid-cols-2 lg:grid-cols-3 sm:gap-5 lg:gap-9\">\n "
},
{
"path": "src/pages/faq/widgets/Questions.vue",
"chars": 6559,
"preview": "<template>\n <VaCard class=\"mb-4\">\n <VaCardContent>\n <h2 class=\"va-h5\">Popular questions</h2>\n <VaAccordion"
},
{
"path": "src/pages/faq/widgets/RequestDemo.vue",
"chars": 1533,
"preview": "<template>\n <VaCard class=\"mb-5 pr-4 flex justify-between\">\n <div>\n <VaCardContent>\n <h2 class=\"va-h5\">G"
},
{
"path": "src/pages/payments/PaymentsPage.vue",
"chars": 916,
"preview": "<template>\n <h1 class=\"page-title\">Payment methods</h1>\n\n <VaCard class=\"mb-6\">\n <VaCardContent>\n <div class=\""
},
{
"path": "src/pages/payments/payment-system/PaymentSystem.stories.ts",
"chars": 313,
"preview": "import PaymentSystem from './PaymentSystem.vue'\n\nexport default {\n title: 'PaymentSystem',\n component: PaymentSystem,\n"
},
{
"path": "src/pages/payments/payment-system/PaymentSystem.vue",
"chars": 440,
"preview": "<template>\n <div class=\"w-20 h-12 bg-backgroundElement rounded flex justify-center items-center align-bottom\">\n <img"
},
{
"path": "src/pages/payments/types.ts",
"chars": 612,
"preview": "export enum PaymentSystemType {\n Visa = 'visa',\n MasterCard = 'mastercard',\n}\n\nexport const paymentSystemTypeOptions ="
},
{
"path": "src/pages/payments/widgets/billing-address/BillingAddressCreateModal.stories.ts",
"chars": 555,
"preview": "import BillingAddressCreateModal from './BillingAddressCreateModal.vue'\n\nexport default {\n components: { BillingAddress"
},
{
"path": "src/pages/payments/widgets/billing-address/BillingAddressCreateModal.vue",
"chars": 1190,
"preview": "<template>\n <VaModal hide-default-actions model-value size=\"small\" close-button @cancel=\"emits('close')\">\n <h3 class"
},
{
"path": "src/pages/payments/widgets/billing-address/BillingAddressEdit.stories.ts",
"chars": 1331,
"preview": "import BillingAddressEdit from './BillingAddressEdit.vue'\nimport { BillingAddress } from '../../types'\n\nexport default {"
},
{
"path": "src/pages/payments/widgets/billing-address/BillingAddressEdit.vue",
"chars": 1973,
"preview": "<template>\n <VaForm ref=\"form\" @submit.prevent=\"submit\">\n <VaInput\n v-model=\"localBillingAddress.name\"\n :r"
},
{
"path": "src/pages/payments/widgets/billing-address/BillingAddressList.stories.ts",
"chars": 283,
"preview": "import BillingAddressList from './BillingAddressList.vue'\n\nexport default {\n title: 'BillingAddressList',\n component: "
},
{
"path": "src/pages/payments/widgets/billing-address/BillingAddressList.vue",
"chars": 2982,
"preview": "<template>\n <div class=\"grid md:grid-cols-2 grid-cols-1 gap-4\">\n <template v-if=\"loading\">\n <div\n v-for="
},
{
"path": "src/pages/payments/widgets/billing-address/BillingAddressListItem.stories.ts",
"chars": 883,
"preview": "import BillingAddressListItem from './BillingAddressListItem.vue'\nimport { BillingAddress } from '../../types'\n\nexport d"
},
{
"path": "src/pages/payments/widgets/billing-address/BillingAddressListItem.vue",
"chars": 1274,
"preview": "<template>\n <div\n class=\"min-h-[114px] p-4 rounded-lg border border-dashed border-backgroundBorder flex flex-col sm:"
},
{
"path": "src/pages/payments/widgets/billing-address/BillingAddressUpdateModal.stories.ts",
"chars": 854,
"preview": "import BillingAddressUpdateModal from './BillingAddressUpdateModal.vue'\nimport { BillingAddress } from '../../types'\n\nex"
},
{
"path": "src/pages/payments/widgets/billing-address/BillingAddressUpdateModal.vue",
"chars": 1084,
"preview": "<template>\n <VaModal hide-default-actions model-value size=\"small\" close-button @cancel=\"emits('close')\">\n <h3 class"
},
{
"path": "src/pages/payments/widgets/my-cards/PaymentCardCreateModal.stories.ts",
"chars": 534,
"preview": "import PaymentCardCreateModal from './PaymentCardCreateModal.vue'\n\nexport default {\n components: { PaymentCardCreateMod"
},
{
"path": "src/pages/payments/widgets/my-cards/PaymentCardCreateModal.vue",
"chars": 1150,
"preview": "<template>\n <VaModal hide-default-actions model-value size=\"small\" close-button @cancel=\"emits('close')\">\n <h3 class"
},
{
"path": "src/pages/payments/widgets/my-cards/PaymentCardEdit.stories.ts",
"chars": 1271,
"preview": "import PaymentCardEdit from './PaymentCardEdit.vue'\nimport { PaymentSystemType, PaymentCard } from '../../types'\n\nexport"
},
{
"path": "src/pages/payments/widgets/my-cards/PaymentCardEdit.vue",
"chars": 2146,
"preview": "<template>\n <VaForm ref=\"form\" @submit.prevent=\"submit\">\n <VaInput\n v-model=\"paymentCardLocal.name\"\n :rule"
},
{
"path": "src/pages/payments/widgets/my-cards/PaymentCardList.stories.ts",
"chars": 265,
"preview": "import PaymentCardList from './PaymentCardList.vue'\n\nexport default {\n title: 'PaymentCardList',\n component: PaymentCa"
},
{
"path": "src/pages/payments/widgets/my-cards/PaymentCardList.vue",
"chars": 2828,
"preview": "<template>\n <div class=\"grid md:grid-cols-2 grid-cols-1 gap-4\">\n <template v-if=\"loading\">\n <div\n v-for="
},
{
"path": "src/pages/payments/widgets/my-cards/PaymentCardListItem.stories.ts",
"chars": 779,
"preview": "import CardListItem from './PaymentCardListItem.vue'\nimport { PaymentSystemType, PaymentCard } from '../../types'\n\nexpor"
},
{
"path": "src/pages/payments/widgets/my-cards/PaymentCardListItem.vue",
"chars": 1502,
"preview": "<template>\n <div\n class=\"min-h-[114px] p-4 rounded-lg border border-dashed border-backgroundBorder flex flex-col sm:"
},
{
"path": "src/pages/payments/widgets/my-cards/PaymentCardUpdateModal.stories.ts",
"chars": 862,
"preview": "import PaymentCardUpdateModal from './PaymentCardUpdateModal.vue'\nimport { PaymentSystemType, PaymentCard } from '../../"
},
{
"path": "src/pages/payments/widgets/my-cards/PaymentCardUpdateModal.vue",
"chars": 1010,
"preview": "<template>\n <VaModal hide-default-actions model-value size=\"small\" close-button @cancel=\"emits('close')\">\n <h3 class"
},
{
"path": "src/pages/preferences/Preferences.vue",
"chars": 971,
"preview": "<template>\n <h1 class=\"page-title\">Preferences</h1>\n <div class=\"flex flex-col p-4 space-y-10 bg-backgroundSecondary r"
},
{
"path": "src/pages/preferences/modals/EditNameModal.vue",
"chars": 1483,
"preview": "<template>\n <VaModal\n :mobile-fullscreen=\"false\"\n size=\"small\"\n hide-default-actions\n max-width=\"380px\"\n "
},
{
"path": "src/pages/preferences/modals/ResetPasswordModal.vue",
"chars": 3317,
"preview": "<template>\n <VaModal\n max-width=\"530px\"\n :mobile-fullscreen=\"false\"\n hide-default-actions\n model-value\n "
},
{
"path": "src/pages/preferences/preferences-header/PreferencesHeader.vue",
"chars": 505,
"preview": "<template>\n <VaAvatar size=\"large\" color=\"warning\"><span class=\"text-4xl\"> 😍 </span></VaAvatar>\n <div class=\"flex flex"
},
{
"path": "src/pages/preferences/settings/Settings.vue",
"chars": 3155,
"preview": "<template>\n <div class=\"flex flex-col md:flex-row md:items-center space-y-2 md:space-y-0 md:space-x-6 min-h-[36px] lead"
},
{
"path": "src/pages/preferences/styles.ts",
"chars": 104,
"preview": "export const buttonStyles = {\n '--va-button-font-size': '14px',\n '--va-button-line-height': '20px',\n}\n"
},
{
"path": "src/pages/pricing-plans/PricingPlans.vue",
"chars": 3730,
"preview": "<template>\n <h1 class=\"page-title\">Choose your plan</h1>\n <div class=\"py-4 text-lg leading-[26px]\">\n If you need mo"
},
{
"path": "src/pages/pricing-plans/options.ts",
"chars": 1202,
"preview": "export type PricingPlans = {\n title: string\n model: string\n badges?: string[]\n description: string\n price: number\n "
},
{
"path": "src/pages/pricing-plans/styles.ts",
"chars": 398,
"preview": "export const badgeStyles = {\n '--va-badge-text-wrapper-line-height': '14px',\n '--va-badge-text-wrapper-letter-spacing'"
},
{
"path": "src/pages/projects/ProjectsPage.vue",
"chars": 4130,
"preview": "<script setup lang=\"ts\">\nimport { ref, provide } from 'vue'\nimport { useLocalStorage } from '@vueuse/core'\nimport { useP"
},
{
"path": "src/pages/projects/components/ProjectStatusBadge.vue",
"chars": 487,
"preview": "<script setup lang=\"ts\">\nimport { PropType } from 'vue'\nimport { Project } from '../types'\n\ndefineProps({\n status: {\n "
},
{
"path": "src/pages/projects/composables/useProjectStatusColor.ts",
"chars": 0,
"preview": ""
},
{
"path": "src/pages/projects/composables/useProjectUsers.ts",
"chars": 982,
"preview": "import { useUsers } from '../../users/composables/useUsers'\nimport { Project } from '../types'\n\nexport function useProje"
},
{
"path": "src/pages/projects/composables/useProjects.ts",
"chars": 2684,
"preview": "import { Ref, ref, unref, computed } from 'vue'\nimport { Sorting, Pagination } from '../../../data/pages/projects'\nimpor"
},
{
"path": "src/pages/projects/types.ts",
"chars": 498,
"preview": "import { User } from '../users/types'\n\nexport type UUID = `${string}-${string}-${string}-${string}-${string}`\n\nexport ty"
},
{
"path": "src/pages/projects/widgets/EditProjectForm.vue",
"chars": 3922,
"preview": "<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { EmptyProject, Project } from '../types'\nimp"
},
{
"path": "src/pages/projects/widgets/ProjectCards.vue",
"chars": 2119,
"preview": "<script setup lang=\"ts\">\nimport { type PropType, inject } from 'vue'\nimport { type Project } from '../types'\nimport Proj"
},
{
"path": "src/pages/projects/widgets/ProjectsTable.vue",
"chars": 4683,
"preview": "<script setup lang=\"ts\">\nimport { PropType, computed, inject } from 'vue'\nimport { defineVaDataTableColumns } from 'vues"
},
{
"path": "src/pages/settings/Settings.vue",
"chars": 1126,
"preview": "<template>\n <div class=\"flex flex-col space-y-6 md:space-y-4\">\n <h1 class=\"page-title\">Settings</h1>\n <div class="
},
{
"path": "src/pages/settings/language-switcher/LanguageSwitcher.vue",
"chars": 1046,
"preview": "<template>\n <div class=\"flex items-center justify-between\">\n <p>Language</p>\n <div class=\"w-40\">\n <VaSelect "
},
{
"path": "src/pages/settings/notifications/Notifications.vue",
"chars": 697,
"preview": "<template>\n <div class=\"flex flex-col p-4 bg-backgroundSecondary rounded-lg\">\n <h3 class=\"h3 mb-6\">Notifications you"
},
{
"path": "src/pages/settings/theme-switcher/ThemeSwitcher.vue",
"chars": 612,
"preview": "<template>\n <VaButtonToggle v-model=\"theme\" color=\"background-element\" border-color=\"background-element\" :options=\"opti"
},
{
"path": "src/pages/users/UsersPage.vue",
"chars": 3864,
"preview": "<script setup lang=\"ts\">\nimport { ref, watchEffect } from 'vue'\nimport UsersTable from './widgets/UsersTable.vue'\nimport"
},
{
"path": "src/pages/users/composables/useUsers.ts",
"chars": 3233,
"preview": "import { Ref, ref, unref, watch, computed } from 'vue'\nimport { v4 as uuid } from 'uuid'\nimport type { Filters, Paginati"
},
{
"path": "src/pages/users/types.ts",
"chars": 298,
"preview": "export type UserRole = 'admin' | 'user' | 'owner'\n\nexport type UUID = `${string}-${string}-${string}-${string}-${string}"
},
{
"path": "src/pages/users/widgets/EditUserForm.vue",
"chars": 4824,
"preview": "<script setup lang=\"ts\">\nimport { PropType, computed, ref, watch } from 'vue'\nimport { useForm } from 'vuestic-ui'\nimpor"
},
{
"path": "src/pages/users/widgets/UserAvatar.vue",
"chars": 963,
"preview": "<script setup lang=\"ts\">\nimport { type PropType } from 'vue'\nimport { type User } from '../types'\n\nconst avatarColor = ("
},
{
"path": "src/pages/users/widgets/UsersTable.vue",
"chars": 5501,
"preview": "<script setup lang=\"ts\">\nimport { defineVaDataTableColumns, useModal } from 'vuestic-ui'\nimport { User, UserRole } from "
},
{
"path": "src/router/index.ts",
"chars": 3193,
"preview": "import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'\n\nimport AuthLayout from '../layouts/AuthLayo"
},
{
"path": "src/scss/icon-fonts/index.scss",
"chars": 215,
"preview": "// These fonts were originally provided by http://weloveiconfonts.com.\n// We decided to add these into package after htt"
},
{
"path": "src/scss/icon-fonts/vuestic-icons/vuestic-icons.scss",
"chars": 1785,
"preview": "@font-face {\n font-family: 'Vuestic Icons';\n font-weight: normal;\n font-style: normal;\n src: url('vuestic-icons.eot'"
},
{
"path": "src/scss/main.scss",
"chars": 1075,
"preview": "@import 'tailwind';\n@import 'vuestic';\n@import 'icon-fonts/index';\n\nbody {\n @apply text-regularMedium;\n\n // TODO Move "
}
]
// ... and 20 more files (download for full content)
About this extraction
This page contains the full source code of the epicmaxco/vuestic-admin GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 220 files (1.8 MB), approximately 626.8k tokens, and a symbol index with 68 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.