Init Boiler Plate
This commit is contained in:
commit
660f994036
8
.editorconfig
Normal file
8
.editorconfig
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
|
||||
charset = utf-8
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
end_of_line = lf
|
||||
max_line_length = 100
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
* text=auto eol=lf
|
||||
36
.gitignore
vendored
Normal file
36
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Cypress
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Vitest
|
||||
__screenshots__/
|
||||
11
.prettierignore
Normal file
11
.prettierignore
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Ignore artifacts:
|
||||
build
|
||||
coverage
|
||||
|
||||
# Ignore all HTML files:
|
||||
**/*.html
|
||||
|
||||
**/.git
|
||||
**/.svn
|
||||
**/.hg
|
||||
**/node_modules
|
||||
10
.prettierrc.json
Normal file
10
.prettierrc.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"printWidth": 120,
|
||||
"htmlWhitespaceSensitivity": "ignore",
|
||||
"vueIndentScriptAndStyle": false,
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
4
.vscode/extensions.json
vendored
Normal file
4
.vscode/extensions.json
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"recommendations": ["prettier.prettier-vscode", "dbaeumer.vscode-eslint", "vue.volar"],
|
||||
"unwantedRecommendations": ["octref.vetur"]
|
||||
}
|
||||
48
README.md
Normal file
48
README.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# vue-bolier-plate
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Recommended Browser Setup
|
||||
|
||||
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
|
||||
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
||||
- Firefox:
|
||||
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
||||
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lint with [ESLint](https://eslint.org/)
|
||||
|
||||
```sh
|
||||
npm run lint
|
||||
```
|
||||
85
eslint.config.js
Normal file
85
eslint.config.js
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import globals from "globals";
|
||||
import pluginJs from "@eslint/js";
|
||||
import tseslint from "typescript-eslint";
|
||||
import pluginVue from "eslint-plugin-vue";
|
||||
import prettier from "eslint-plugin-prettier/recommended";
|
||||
import vueConfigTypescript from "@vue/eslint-config-typescript";
|
||||
import vueConfigPrettier from "@vue/eslint-config-prettier";
|
||||
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
DOLPHIN: "readonly",
|
||||
},
|
||||
},
|
||||
},
|
||||
// js
|
||||
pluginJs.configs.recommended,
|
||||
{
|
||||
rules: {
|
||||
"no-unused-vars": "error",
|
||||
"no-undef": "error",
|
||||
},
|
||||
},
|
||||
// ts
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
rules: {
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^(props|_)",
|
||||
varsIgnorePattern: "^(props|_)",
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
},
|
||||
},
|
||||
// vue
|
||||
...pluginVue.configs["flat/recommended"],
|
||||
{
|
||||
files: ["*.vue", "**/*.vue"],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: tseslint.parser,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
...vueConfigTypescript.rules,
|
||||
...vueConfigPrettier.rules,
|
||||
"prettier/prettier": [
|
||||
"warn",
|
||||
{
|
||||
singleQuote: false,
|
||||
},
|
||||
],
|
||||
"vue/multi-word-component-names": "off",
|
||||
"vue/attribute-hyphenation": "off",
|
||||
"vue/no-v-html": "off",
|
||||
"vue/v-on-event-hyphenation": "off",
|
||||
"vue/require-v-for-key": "off",
|
||||
"vue/no-template-shadow": "error",
|
||||
"vue/no-reserved-component-names": "off",
|
||||
"vue/attributes-order": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-require-imports": "off",
|
||||
"vue/require-default-prop": "error",
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: ["node_modules", ".nuxt", ".output", "dist"],
|
||||
},
|
||||
// prettier
|
||||
prettier,
|
||||
{
|
||||
rules: {
|
||||
"prettier/prettier": ["warn", { singleQuote: false }],
|
||||
},
|
||||
},
|
||||
];
|
||||
13
index.html
Normal file
13
index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body class="font-open-sans">
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/initApp.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
5778
package-lock.json
generated
Normal file
5778
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
47
package.json
Normal file
47
package.json
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"name": "vue-bolier-plate",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite --host",
|
||||
"prebuild": "npm run lint",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build",
|
||||
"format": "prettier --write src/",
|
||||
"format:check": "prettier --check src/",
|
||||
"lint": "eslint src --max-warnings 0",
|
||||
"lint:fix": "eslint src --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.13.2",
|
||||
"dolphin-components": "^3.1.0",
|
||||
"nepali-date-library": "^1.1.11",
|
||||
"pinia": "^3.0.4",
|
||||
"typescript-eslint": "^8.50.0",
|
||||
"vue": "^3.5.25",
|
||||
"vue-router": "^4.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node24": "^24.0.3",
|
||||
"@types/node": "^25.0.3",
|
||||
"@vitejs/plugin-vue": "^6.0.3",
|
||||
"@vue/eslint-config-prettier": "^10.2.0",
|
||||
"@vue/eslint-config-typescript": "^14.6.0",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-plugin-vue": "~10.6.2",
|
||||
"jiti": "^2.6.1",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"prettier": "3.7.4",
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^7.3.0",
|
||||
"vite-plugin-vue-devtools": "^8.0.5",
|
||||
"vue-tsc": "^3.1.8"
|
||||
}
|
||||
}
|
||||
9
public/config.example.json
Normal file
9
public/config.example.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"ENVIRONMENT": "PRODUCTION || DEVELOPMENT",
|
||||
"PAGE_TITLE": "Dolphin",
|
||||
"PAGE_TITLE_LOGO": "http://localhost:3000/img/Dolphin/dolphinfav.ico",
|
||||
"CLIENT_NAME": "Mavorion Systems",
|
||||
"CLIENT_LOGO": "",
|
||||
"CLIENT_LOCATION": "LAZIMPAT",
|
||||
"API_BASE_URL": "http://localhost:8000/api/"
|
||||
}
|
||||
9
public/config.json
Normal file
9
public/config.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"ENVIRONMENT": "PRODUCTION || DEVELOPMENT",
|
||||
"PAGE_TITLE": "Dolphin",
|
||||
"PAGE_TITLE_LOGO": "/img/Dolphin/dolphinfav.ico",
|
||||
"CLIENT_NAME": "Mavorion Systems",
|
||||
"CLIENT_LOCATION": "LAZIMPAT",
|
||||
"CLIENT_LOGO": "",
|
||||
"API_BASE_URL": "http://localhost:8000/api/v1/"
|
||||
}
|
||||
BIN
public/img/Dolphin/dolphin-logo.png
Normal file
BIN
public/img/Dolphin/dolphin-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
BIN
public/img/Dolphin/dolphinfav.ico
Normal file
BIN
public/img/Dolphin/dolphinfav.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 361 KiB |
1
public/svg/large-triangles.svg
Normal file
1
public/svg/large-triangles.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns='http://www.w3.org/2000/svg' width='750' height='625' viewBox='0 0 1080 900'><rect fill='#F6F8FF' width='1080' height='900'/><g fill-opacity='0.02'><polygon fill='#444' points='90 150 0 300 180 300'/><polygon points='90 150 180 0 0 0'/><polygon fill='#AAA' points='270 150 360 0 180 0'/><polygon fill='#DDD' points='450 150 360 300 540 300'/><polygon fill='#999' points='450 150 540 0 360 0'/><polygon points='630 150 540 300 720 300'/><polygon fill='#DDD' points='630 150 720 0 540 0'/><polygon fill='#444' points='810 150 720 300 900 300'/><polygon fill='#FFF' points='810 150 900 0 720 0'/><polygon fill='#DDD' points='990 150 900 300 1080 300'/><polygon fill='#444' points='990 150 1080 0 900 0'/><polygon fill='#DDD' points='90 450 0 600 180 600'/><polygon points='90 450 180 300 0 300'/><polygon fill='#666' points='270 450 180 600 360 600'/><polygon fill='#AAA' points='270 450 360 300 180 300'/><polygon fill='#DDD' points='450 450 360 600 540 600'/><polygon fill='#999' points='450 450 540 300 360 300'/><polygon fill='#999' points='630 450 540 600 720 600'/><polygon fill='#FFF' points='630 450 720 300 540 300'/><polygon points='810 450 720 600 900 600'/><polygon fill='#DDD' points='810 450 900 300 720 300'/><polygon fill='#AAA' points='990 450 900 600 1080 600'/><polygon fill='#444' points='990 450 1080 300 900 300'/><polygon fill='#222' points='90 750 0 900 180 900'/><polygon points='270 750 180 900 360 900'/><polygon fill='#DDD' points='270 750 360 600 180 600'/><polygon points='450 750 540 600 360 600'/><polygon points='630 750 540 900 720 900'/><polygon fill='#444' points='630 750 720 600 540 600'/><polygon fill='#AAA' points='810 750 720 900 900 900'/><polygon fill='#666' points='810 750 900 600 720 600'/><polygon fill='#999' points='990 750 900 900 1080 900'/><polygon fill='#999' points='180 0 90 150 270 150'/><polygon fill='#444' points='360 0 270 150 450 150'/><polygon fill='#FFF' points='540 0 450 150 630 150'/><polygon points='900 0 810 150 990 150'/><polygon fill='#222' points='0 300 -90 450 90 450'/><polygon fill='#FFF' points='0 300 90 150 -90 150'/><polygon fill='#FFF' points='180 300 90 450 270 450'/><polygon fill='#666' points='180 300 270 150 90 150'/><polygon fill='#222' points='360 300 270 450 450 450'/><polygon fill='#FFF' points='360 300 450 150 270 150'/><polygon fill='#444' points='540 300 450 450 630 450'/><polygon fill='#222' points='540 300 630 150 450 150'/><polygon fill='#AAA' points='720 300 630 450 810 450'/><polygon fill='#666' points='720 300 810 150 630 150'/><polygon fill='#FFF' points='900 300 810 450 990 450'/><polygon fill='#999' points='900 300 990 150 810 150'/><polygon points='0 600 -90 750 90 750'/><polygon fill='#666' points='0 600 90 450 -90 450'/><polygon fill='#AAA' points='180 600 90 750 270 750'/><polygon fill='#444' points='180 600 270 450 90 450'/><polygon fill='#444' points='360 600 270 750 450 750'/><polygon fill='#999' points='360 600 450 450 270 450'/><polygon fill='#666' points='540 600 630 450 450 450'/><polygon fill='#222' points='720 600 630 750 810 750'/><polygon fill='#FFF' points='900 600 810 750 990 750'/><polygon fill='#222' points='900 600 990 450 810 450'/><polygon fill='#DDD' points='0 900 90 750 -90 750'/><polygon fill='#444' points='180 900 270 750 90 750'/><polygon fill='#FFF' points='360 900 450 750 270 750'/><polygon fill='#AAA' points='540 900 630 750 450 750'/><polygon fill='#FFF' points='720 900 810 750 630 750'/><polygon fill='#222' points='900 900 990 750 810 750'/><polygon fill='#222' points='1080 300 990 450 1170 450'/><polygon fill='#FFF' points='1080 300 1170 150 990 150'/><polygon points='1080 600 990 750 1170 750'/><polygon fill='#666' points='1080 600 1170 450 990 450'/><polygon fill='#DDD' points='1080 900 1170 750 990 750'/></g></svg>
|
||||
5
src/App.vue
Normal file
5
src/App.vue
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<RouterView></RouterView>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
2
src/assets/css/main.css
Normal file
2
src/assets/css/main.css
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
@import "./tailwind.css";
|
||||
@import "dolphin-components/dolphin-components.css";
|
||||
36
src/assets/css/tailwind.css
Normal file
36
src/assets/css/tailwind.css
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
|
||||
--color-modal: var(--card);
|
||||
--color-modal-foreground: var(--card-foreground);
|
||||
|
||||
--color-modal: var(--card);
|
||||
--color-modal-foreground: var(--card-foreground);
|
||||
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
|
||||
--color-border: var(--border);
|
||||
--color-border-foreground: var(--border-foreground);
|
||||
--color-input: var(--input);
|
||||
--color-input-focus: var(--input-focus);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-scrollbar: var(--scrollbar);
|
||||
|
||||
--radius-default: var(--radius);
|
||||
}
|
||||
0
src/assets/ts/main.ts
Normal file
0
src/assets/ts/main.ts
Normal file
56
src/core/app/api.ts
Normal file
56
src/core/app/api.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { useAuth } from "@/stores/Auth/auth.store";
|
||||
import axios from "axios";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { auth } from "./endpoints";
|
||||
|
||||
const apiURL: string = DOLPHIN.config.API_BASE_URL;
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: apiURL,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
// No need to save the Token as they are already attached to the cookie from backend
|
||||
api.interceptors.request.use(
|
||||
function (config) {
|
||||
return config;
|
||||
},
|
||||
function (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
api.interceptors.response.use(
|
||||
function (response) {
|
||||
return response;
|
||||
},
|
||||
async function (error) {
|
||||
const {
|
||||
config,
|
||||
response: { status },
|
||||
} = error;
|
||||
const originalRequest = config;
|
||||
if (status === 401 && !originalRequest._retry) {
|
||||
originalRequest._retry = true;
|
||||
const authStore = useAuth();
|
||||
const { isAuthenticated } = storeToRefs(authStore);
|
||||
try {
|
||||
await api.post(auth.tokenRefresh, null, {
|
||||
withCredentials: true,
|
||||
});
|
||||
isAuthenticated.value = true;
|
||||
return await axios(originalRequest);
|
||||
} catch (e) {
|
||||
console.error("Token refresh failed", e);
|
||||
isAuthenticated.value = false;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default api;
|
||||
31
src/core/app/endpoints.ts
Normal file
31
src/core/app/endpoints.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
export const auth = {
|
||||
login: `/login/`,
|
||||
logout: `/logout/`,
|
||||
tokenRefresh: `/token/refresh/`,
|
||||
};
|
||||
|
||||
export const user = {
|
||||
list: `/user/`,
|
||||
create: `/user/`,
|
||||
retrieve: (id: number) => `/user/${id}/`,
|
||||
update: (id: number) => `/user/${id}/`,
|
||||
partialUpdate: (id: number) => `/user/${id}/`,
|
||||
delete: (id: number) => `/user/${id}/`,
|
||||
self: `/user/self/`,
|
||||
};
|
||||
|
||||
export const permissions = {
|
||||
allPermissions: `/all-permissions/`,
|
||||
allGroupsPermissions: `/all-groups-permissions/`,
|
||||
createGroupsPermissions: `/create-groups-permissions/`,
|
||||
assignGroupsToUser: `/assign-groups-to-user/`,
|
||||
createRoleDepartmentAccess: `/create-role-department-access/`,
|
||||
};
|
||||
|
||||
export const API_ENDPOINTS = {
|
||||
auth,
|
||||
user,
|
||||
permissions,
|
||||
};
|
||||
|
||||
export type ApiEndpoints = typeof API_ENDPOINTS;
|
||||
15
src/core/declarations/dolphin.d.ts
vendored
Normal file
15
src/core/declarations/dolphin.d.ts
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
export {};
|
||||
|
||||
declare global {
|
||||
var DOLPHIN: {
|
||||
config: {
|
||||
ENVIRONMENT: "PRODUCTION" | "DEVELOPMENT";
|
||||
PAGE_TITLE: string;
|
||||
PAGE_TITLE_LOGO: string;
|
||||
CLIENT_NAME: string;
|
||||
CLIENT_LOGO: string;
|
||||
CLIENT_LOCATION: string;
|
||||
API_BASE_URL: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
30
src/core/setup/components.setup.ts
Normal file
30
src/core/setup/components.setup.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import type { App } from "vue";
|
||||
import {
|
||||
ContentLayout,
|
||||
DateRange,
|
||||
DateSelector,
|
||||
Icons,
|
||||
Modal,
|
||||
Switch,
|
||||
Tabulator,
|
||||
Toggle,
|
||||
Towser,
|
||||
Loader,
|
||||
Wizard,
|
||||
} from "dolphin-components";
|
||||
|
||||
const setupComponents = (app: App) => {
|
||||
app.component("ContentLayout", ContentLayout);
|
||||
app.component("DateRange", DateRange);
|
||||
app.component("DateSelector", DateSelector);
|
||||
app.component("Icons", Icons);
|
||||
app.component("Modal", Modal);
|
||||
app.component("Switch", Switch);
|
||||
app.component("Tabulator", Tabulator);
|
||||
app.component("Toggle", Toggle);
|
||||
app.component("Towser", Towser);
|
||||
app.component("Loader", Loader);
|
||||
app.component("Wizard", Wizard);
|
||||
};
|
||||
|
||||
export default setupComponents;
|
||||
20
src/core/setup/directives.setup.ts
Normal file
20
src/core/setup/directives.setup.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import type { App } from "vue";
|
||||
import {
|
||||
InputError,
|
||||
ScrollbarScroll,
|
||||
InputSelect,
|
||||
InputCurrency,
|
||||
ScrollbarAnimateDropdown,
|
||||
Tooltip,
|
||||
} from "dolphin-components";
|
||||
|
||||
const setupDirectives = (app: App) => {
|
||||
app.directive("input-error", InputError);
|
||||
app.directive("input-currency", InputCurrency);
|
||||
app.directive("input-select", InputSelect);
|
||||
app.directive("hide-scrollbar", ScrollbarScroll);
|
||||
app.directive("animate-dropdown", ScrollbarAnimateDropdown);
|
||||
app.directive("tooltip", Tooltip);
|
||||
};
|
||||
|
||||
export default setupDirectives;
|
||||
11
src/core/setup/plugins.setup.ts
Normal file
11
src/core/setup/plugins.setup.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import type { App } from "vue";
|
||||
import { FocusNextPlugin, CurrencyFormatterPlugin, NumbertoWordPlugin, ToastPlugin } from "dolphin-components";
|
||||
|
||||
const setupPlugins = (app: App) => {
|
||||
app.use(FocusNextPlugin);
|
||||
app.use(CurrencyFormatterPlugin);
|
||||
app.use(NumbertoWordPlugin);
|
||||
app.use(ToastPlugin);
|
||||
};
|
||||
|
||||
export default setupPlugins;
|
||||
25
src/core/setup/router.setup.ts
Normal file
25
src/core/setup/router.setup.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import router from "@/router";
|
||||
import { useAuth } from "@/stores/Auth/auth.store";
|
||||
import type { App } from "vue";
|
||||
|
||||
const setupRouter = (app: App) => {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
if (to.meta.requireAuth || to.meta.onlyGuest) {
|
||||
const authStore = useAuth();
|
||||
await authStore.getUser();
|
||||
if (authStore.isAuthenticated && to.meta.requireAuth) {
|
||||
next();
|
||||
} else if (!authStore.isAuthenticated && to.meta.onlyGuest) {
|
||||
next();
|
||||
} else if (authStore.isAuthenticated && to.meta.onlyGuest) {
|
||||
next({ name: "dashboard" });
|
||||
} else {
|
||||
next({ name: "login" });
|
||||
}
|
||||
}
|
||||
next();
|
||||
});
|
||||
app.use(router);
|
||||
};
|
||||
|
||||
export default setupRouter;
|
||||
11
src/core/types/app/sidebar.type.ts
Normal file
11
src/core/types/app/sidebar.type.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import type { RouteLocationRaw } from "vue-router";
|
||||
|
||||
export type SidebarItem = {
|
||||
label: string;
|
||||
to?: RouteLocationRaw;
|
||||
icon?: string;
|
||||
children?: {
|
||||
label: string;
|
||||
to: RouteLocationRaw;
|
||||
}[];
|
||||
};
|
||||
15
src/core/types/auth/auth.type.ts
Normal file
15
src/core/types/auth/auth.type.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
export interface LoginDetails {
|
||||
username: string;
|
||||
password: string;
|
||||
cloudflareToken?: string;
|
||||
}
|
||||
|
||||
export interface UserDetails {
|
||||
id: number;
|
||||
username: string;
|
||||
full_name: string;
|
||||
email: string;
|
||||
phone: number | null;
|
||||
department: string | null;
|
||||
roles: [];
|
||||
}
|
||||
11
src/core/utils/Common.ts
Normal file
11
src/core/utils/Common.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
const getNameInitials = (name: string): string => {
|
||||
if (!name?.trim()) return "";
|
||||
|
||||
const parts = name.trim().split(/\s+/);
|
||||
const first = parts[0]?.[0]?.toUpperCase() ?? "";
|
||||
const last = (parts.length > 1 ? parts[parts.length - 1]?.[0]?.toUpperCase() : "") ?? "";
|
||||
|
||||
return first + last;
|
||||
};
|
||||
|
||||
export { getNameInitials };
|
||||
45
src/initApp.ts
Normal file
45
src/initApp.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
globalThis.DOLPHIN = globalThis.DOLPHIN || { config: {} as any };
|
||||
|
||||
async function loadConfig() {
|
||||
try {
|
||||
const response = await fetch("/config.json");
|
||||
if (!response.ok) throw new Error("Configuration Missing");
|
||||
|
||||
DOLPHIN.config = Object.freeze(await response.json());
|
||||
document.title = DOLPHIN.config.PAGE_TITLE;
|
||||
|
||||
document.querySelectorAll('link[rel*="icon"]').forEach((el) => el.remove());
|
||||
const link = Object.assign(document.createElement("link"), {
|
||||
rel: "icon",
|
||||
type: "image/png",
|
||||
href: `${DOLPHIN.config.PAGE_TITLE_LOGO}`,
|
||||
});
|
||||
document.head.appendChild(link);
|
||||
|
||||
if (DOLPHIN.config.ENVIRONMENT == "PRODUCTION") {
|
||||
setInterval(() => {
|
||||
console.clear();
|
||||
const style = `
|
||||
color: #00CCFF;
|
||||
font-size: 64px;
|
||||
font-weight: bold;
|
||||
text-shadow: 1px 1px 2px #000;
|
||||
`;
|
||||
console.log("%cDolphin Kiosk - Mavorion", style);
|
||||
}, 1000);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Configuration Missing:", e);
|
||||
const msg = document.createElement("div");
|
||||
msg.innerText = "⚠️ Configuration Missing. Please contact support.";
|
||||
msg.style.cssText = "color:red; font-family:sans-serif; text-align:center; margin-top:50px;";
|
||||
document.body.appendChild(msg);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await loadConfig();
|
||||
const { initAPP } = await import("./main");
|
||||
initAPP();
|
||||
})();
|
||||
148
src/layouts/AuthLayout.vue
Normal file
148
src/layouts/AuthLayout.vue
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
<template>
|
||||
<div class="app-container" :class="coreStore.isSidebarOpen ? 'sidebar-open' : 'sidebar-close'">
|
||||
<Header></Header>
|
||||
<div class="body-container">
|
||||
<Sidebar :sidebarItems="sidebarItems"></Sidebar>
|
||||
<div class="main-container">
|
||||
<div class="relative">
|
||||
<RouterView />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import Footer from "./components/Footer.vue";
|
||||
import Sidebar from "./components/Sidebar.vue";
|
||||
import Header from "./components/Header.vue";
|
||||
import { useCore } from "@/stores/App/core.store";
|
||||
|
||||
const coreStore = useCore();
|
||||
|
||||
import type { SidebarItem } from "@/core/types/app/sidebar.type";
|
||||
|
||||
const sidebarItems: SidebarItem[] = [
|
||||
{
|
||||
label: "Dashboard",
|
||||
},
|
||||
{
|
||||
label: "Dashboard",
|
||||
icon: "Home",
|
||||
to: "/",
|
||||
},
|
||||
{
|
||||
label: "Stock Verification",
|
||||
icon: "Blocks",
|
||||
children: [
|
||||
{
|
||||
label: "Opening Stock",
|
||||
to: "/stock-verification/opening-stock/",
|
||||
},
|
||||
{
|
||||
label: "Closing Stock",
|
||||
to: "/stock-verification/closing-stock/",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Components",
|
||||
},
|
||||
{
|
||||
label: "Content Layout",
|
||||
icon: "Package",
|
||||
to: "/component/content-layout",
|
||||
},
|
||||
{
|
||||
label: "Modal",
|
||||
icon: "MessageSquareCode",
|
||||
to: "/component/modal",
|
||||
},
|
||||
{
|
||||
label: "Switch",
|
||||
icon: "ToggleLeft",
|
||||
to: "/component/switch",
|
||||
},
|
||||
{
|
||||
label: "Toggle",
|
||||
icon: "ToggleRight",
|
||||
to: "/component/toggle",
|
||||
},
|
||||
{
|
||||
label: "Tabulator",
|
||||
icon: "Table",
|
||||
to: "/component/tabulator",
|
||||
},
|
||||
{
|
||||
label: "Towser",
|
||||
icon: "Blinds",
|
||||
to: "/component/towser",
|
||||
},
|
||||
{
|
||||
label: "Date Range",
|
||||
icon: "CalendarRange",
|
||||
to: "/component/date-range",
|
||||
},
|
||||
{
|
||||
label: "Date Selector",
|
||||
icon: "Calendar",
|
||||
to: "/component/date-selector",
|
||||
},
|
||||
{
|
||||
label: "Wizard Stepper",
|
||||
icon: "WandSparkles",
|
||||
to: "/component/wizard",
|
||||
},
|
||||
{
|
||||
label: "Loader",
|
||||
icon: "Loader",
|
||||
to: "/component/loader",
|
||||
},
|
||||
// {
|
||||
// label: "ImageCropper Upload",
|
||||
// icon: "Crop",
|
||||
// to: "/component/image-cropper",
|
||||
// },
|
||||
{
|
||||
label: "Directives",
|
||||
},
|
||||
{
|
||||
label: "Input Error",
|
||||
icon: "TriangleAlert",
|
||||
to: "/directives/input-error",
|
||||
},
|
||||
{
|
||||
label: "Input Currency",
|
||||
icon: "CircleDollarSign",
|
||||
to: "/directives/input-currency",
|
||||
},
|
||||
{
|
||||
label: "Input Select",
|
||||
icon: "TextCursorInput",
|
||||
to: "/directives/input-select",
|
||||
},
|
||||
{
|
||||
label: "Tooltip",
|
||||
icon: "BookOpenText",
|
||||
to: "/directives/tooltip",
|
||||
},
|
||||
{
|
||||
label: "CSS",
|
||||
},
|
||||
{
|
||||
label: "Buttons",
|
||||
icon: "SquareArrowLeft",
|
||||
to: "/css/button",
|
||||
},
|
||||
{
|
||||
label: "Input",
|
||||
icon: "TextCursor",
|
||||
to: "/css/input",
|
||||
},
|
||||
{
|
||||
label: "Font",
|
||||
icon: "CaseSensitive",
|
||||
to: "/css/font",
|
||||
},
|
||||
];
|
||||
</script>
|
||||
12
src/layouts/components/Footer.vue
Normal file
12
src/layouts/components/Footer.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<div class="footer-container bg-white">
|
||||
<div class="flex gap-0.5 justify-end h-full">
|
||||
<span class="h-fit my-auto text-[13px]">
|
||||
<span style="font-family: sans-serif" class="font-bold">©</span>
|
||||
2025, Mavorion Systems. All rights reserved.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
101
src/layouts/components/Header.vue
Normal file
101
src/layouts/components/Header.vue
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<template>
|
||||
<nav class="header-container">
|
||||
<div class="px-3.75 flex justify-between w-full">
|
||||
<div class="w-[225px] flex justify-center">
|
||||
<img src="/img/Dolphin/dolphin-logo.png" alt="" class="w-32 my-auto" />
|
||||
</div>
|
||||
|
||||
<div class="relative select-none" id="modal-div-nav-profile">
|
||||
<div
|
||||
class="w-[38px] rounded-full text-white h-[38px] flex justify-center bg-primary cursor-pointer transition-all"
|
||||
@click="isUserMenuOpen = !isUserMenuOpen"
|
||||
:class="
|
||||
isUserMenuOpen ? 'shadow-[0px_0px_3px_3px_#004cad6e]' : 'shadow-[0px_0px_0px_0px_#004cad6e]'
|
||||
"
|
||||
>
|
||||
<span v-if="false" class="h-fit my-auto text-[16px]">
|
||||
<img src="" alt="Profile photo" class="w-[32px] h-[32px] object-cover rounded-full" />
|
||||
</span>
|
||||
<span v-else class="h-[30px] my-auto text-[20px]">
|
||||
{{ getNameInitials(user.full_name ? user.full_name : user.username) }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
:class="isUserMenuOpen ? 'block' : 'hidden'"
|
||||
class="absolute bg-white right-0 w-[250px] border shadow-sm transition-all"
|
||||
tabindex="0"
|
||||
ref="userProfileMenu"
|
||||
>
|
||||
<div>
|
||||
<div class="px-[15px] py-[5px] border-b">
|
||||
<div class="text-[12px] font-bold">Account</div>
|
||||
<div class="flex gap-[5px]">
|
||||
<div
|
||||
class="text-white w-[42px] h-[42px] rounded-full flex justify-center item-center bg-primary"
|
||||
@click="action('profile')"
|
||||
>
|
||||
<span v-if="false" class="h-fit my-auto text-[14px]">
|
||||
<img
|
||||
src=""
|
||||
alt="Profile photo"
|
||||
class="w-[42px] h-[42px] object-cover rounded-full"
|
||||
/>
|
||||
</span>
|
||||
<span v-else class="h-[38px] my-auto text-[28px]">
|
||||
{{ getNameInitials(user.full_name ? user.full_name : user.username) }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div>{{ user.full_name ? user.full_name : user.username }}</div>
|
||||
<div class="text-[12px] text-secondary-foreground">{{ user.email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="px-[15px] py-[5px] hover:bg-secondary cursor-pointer text-red-500"
|
||||
@click="action('logout')"
|
||||
>
|
||||
Logout
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getNameInitials } from "@/core/utils/Common";
|
||||
import { useAuth } from "@/stores/Auth/auth.store";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
|
||||
const authStore = useAuth();
|
||||
const isUserMenuOpen = ref(false);
|
||||
|
||||
const { user } = storeToRefs(authStore);
|
||||
|
||||
function handleDocumentClick(event: any) {
|
||||
const element = document.getElementById("modal-div-nav-profile");
|
||||
if (isUserMenuOpen.value && !(event.target == element || element?.contains(event.target))) {
|
||||
isUserMenuOpen.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
document.addEventListener("click", handleDocumentClick);
|
||||
});
|
||||
onUnmounted(async () => {
|
||||
document.removeEventListener("click", handleDocumentClick);
|
||||
});
|
||||
|
||||
const action = (type: string) => {
|
||||
switch (type) {
|
||||
case "logout":
|
||||
authStore.logout();
|
||||
break;
|
||||
}
|
||||
|
||||
isUserMenuOpen.value = false;
|
||||
};
|
||||
</script>
|
||||
104
src/layouts/components/Sidebar.vue
Normal file
104
src/layouts/components/Sidebar.vue
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<div class="sidebar-container border-r" v-hide-scrollbar>
|
||||
<button v-if="coreStore.isSidebarOpen" @click="coreStore.setSidebarStatus(false)" class="sidebar-close-item">
|
||||
<Icons name="ArrowLeft" size="24" />
|
||||
</button>
|
||||
<button v-if="!coreStore.isSidebarOpen" class="sidebar-open-item" @click="coreStore.setSidebarStatus(true)">
|
||||
<Icons name="TextAlignJustify" size="30" />
|
||||
</button>
|
||||
<div class="sidebar-items">
|
||||
<div v-for="(item, index) in sidebarItems">
|
||||
<div class="sidebar-header" v-if="!item.to && !item.children">
|
||||
{{ item.label }}
|
||||
</div>
|
||||
<RouterLink
|
||||
:to="item.to"
|
||||
v-if="item.to && !item.children"
|
||||
class="sidebar-default"
|
||||
:class="isActive(item) ? 'sidebar-item-active' : ''"
|
||||
@click="isActive(item) ? toggleDropdown(-1) : null"
|
||||
>
|
||||
<div>
|
||||
<Icons :name="item.icon" size="20" />
|
||||
</div>
|
||||
<span>{{ item.label }}</span>
|
||||
</RouterLink>
|
||||
<div
|
||||
v-if="item.children && !item.to"
|
||||
class="sidebar-dropdown"
|
||||
:class="activeDropdown == index ? 'sidebar-item-open' : ''"
|
||||
>
|
||||
<div class="sidebar-dropdown-header" @click="toggleDropdown(index)">
|
||||
<div>
|
||||
<Icons :name="item.icon" size="20" />
|
||||
</div>
|
||||
<span>{{ item.label }}</span>
|
||||
<div class="sidebar-chevron">
|
||||
<Icons name="ChevronRight" size="14" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-dropdown-items" v-animate-dropdown>
|
||||
<div class="sidebar-dropdown-item" v-for="childitem in item.children">
|
||||
<!-- v-if="childitem.permission ? hasPermission(childitem.permission) : true" -->
|
||||
<RouterLink :to="childitem.to" :class="isActive(childitem) ? 'sidebar-item-active' : ''">
|
||||
<span>{{ childitem.label }}</span>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, type PropType, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import type { SidebarItem } from "@/core/types/app/sidebar.type";
|
||||
import { useCore } from "@/stores/App/core.store";
|
||||
|
||||
const router = useRouter();
|
||||
const coreStore = useCore();
|
||||
|
||||
const props = defineProps({
|
||||
sidebarItems: {
|
||||
type: Array as PropType<SidebarItem[]>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const activeDropdown = ref(-1);
|
||||
|
||||
const toggleDropdown = (val: number) => {
|
||||
if (activeDropdown.value == val) {
|
||||
activeDropdown.value = -1;
|
||||
} else {
|
||||
activeDropdown.value = val;
|
||||
}
|
||||
};
|
||||
|
||||
const isActive = (item: SidebarItem) => {
|
||||
if (item.to == router.currentRoute.value.path) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
checkDropDown();
|
||||
});
|
||||
|
||||
const checkDropDown = () => {
|
||||
if (props.sidebarItems) {
|
||||
props.sidebarItems.forEach((item) => {
|
||||
if (item.children && !item.to) {
|
||||
item.children.forEach((child) => {
|
||||
if (child.to == router.currentRoute.value.path) {
|
||||
toggleDropdown(props.sidebarItems.indexOf(item));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
26
src/main.ts
Normal file
26
src/main.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// @ts-ignore
|
||||
import "@/assets/css/main.css";
|
||||
|
||||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
|
||||
import App from "./App.vue";
|
||||
|
||||
//Setups
|
||||
import setupComponents from "./core/setup/components.setup";
|
||||
import setupDirectives from "./core/setup/directives.setup";
|
||||
import setupPlugins from "./core/setup/plugins.setup";
|
||||
import setupRouter from "./core/setup/router.setup";
|
||||
|
||||
export const initAPP = () => {
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(createPinia());
|
||||
|
||||
setupComponents(app);
|
||||
setupDirectives(app);
|
||||
setupPlugins(app);
|
||||
setupRouter(app);
|
||||
|
||||
app.mount("#app");
|
||||
};
|
||||
119
src/router/auth.router.ts
Normal file
119
src/router/auth.router.ts
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
import type { RouteRecordRaw } from "vue-router";
|
||||
|
||||
const authChildren: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: "/",
|
||||
name: "dashboard",
|
||||
component: () => import("@/views/Dashboard/Index.vue"),
|
||||
},
|
||||
{
|
||||
path: "/component",
|
||||
name: "component",
|
||||
children: [
|
||||
{
|
||||
path: "content-layout",
|
||||
name: "content-layout",
|
||||
component: () => import("@/views/Components/ContentLayout.vue"),
|
||||
},
|
||||
{
|
||||
path: "modal",
|
||||
name: "modal",
|
||||
component: () => import("@/views/Components/Modal.vue"),
|
||||
},
|
||||
{
|
||||
path: "switch",
|
||||
name: "switch",
|
||||
component: () => import("@/views/Components/Switch.vue"),
|
||||
},
|
||||
{
|
||||
path: "toggle",
|
||||
name: "toggle",
|
||||
component: () => import("@/views/Components/Toggle.vue"),
|
||||
},
|
||||
{
|
||||
path: "towser",
|
||||
name: "towser",
|
||||
component: () => import("@/views/Components/Towser.vue"),
|
||||
},
|
||||
{
|
||||
path: "tabulator",
|
||||
name: "tabulator",
|
||||
component: () => import("@/views/Components/Tabulator.vue"),
|
||||
},
|
||||
{
|
||||
path: "date-range",
|
||||
name: "date-range",
|
||||
component: () => import("@/views/Components/DateRange.vue"),
|
||||
},
|
||||
{
|
||||
path: "date-selector",
|
||||
name: "date-selector",
|
||||
component: () => import("@/views/Components/DateSelector.vue"),
|
||||
},
|
||||
{
|
||||
path: "wizard",
|
||||
name: "wizard",
|
||||
component: () => import("@/views/Components/Wizard.vue"),
|
||||
},
|
||||
{
|
||||
path: "loader",
|
||||
name: "loader",
|
||||
component: () => import("@/views/Components/Loader.vue"),
|
||||
},
|
||||
{
|
||||
path: "image-cropper",
|
||||
name: "image-cropper",
|
||||
component: () => import("@/views/Components/ImageCropper.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/directives",
|
||||
name: "directives",
|
||||
children: [
|
||||
{
|
||||
path: "input-error",
|
||||
name: "input-error",
|
||||
component: () => import("@/views/Directives/InputError.vue"),
|
||||
},
|
||||
{
|
||||
path: "input-currency",
|
||||
name: "input-currency",
|
||||
component: () => import("@/views/Directives/InputCurrency.vue"),
|
||||
},
|
||||
{
|
||||
path: "input-select",
|
||||
name: "input-select",
|
||||
component: () => import("@/views/Directives/InputSelect.vue"),
|
||||
},
|
||||
{
|
||||
path: "tooltip",
|
||||
name: "tooltip",
|
||||
component: () => import("@/views/Directives/Tooltip.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/css",
|
||||
name: "css",
|
||||
children: [
|
||||
{
|
||||
path: "button",
|
||||
name: "button",
|
||||
component: () => import("@/views/CSS/Button.vue"),
|
||||
},
|
||||
{
|
||||
path: "input",
|
||||
name: "input",
|
||||
component: () => import("@/views/CSS/Input.vue"),
|
||||
},
|
||||
{
|
||||
path: "font",
|
||||
name: "font",
|
||||
component: () => import("@/views/CSS/Font.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default authChildren;
|
||||
27
src/router/index.ts
Normal file
27
src/router/index.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import authChildren from "./auth.router";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
{
|
||||
path: "/",
|
||||
name: "authLayout",
|
||||
component: () => import("@/layouts/AuthLayout.vue"),
|
||||
meta: {
|
||||
requireAuth: true,
|
||||
},
|
||||
children: authChildren,
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
name: "login",
|
||||
meta: {
|
||||
onlyGuest: true,
|
||||
},
|
||||
component: () => import("@/views/Auth/Login.vue"),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export default router;
|
||||
10
src/router/router.ts
Normal file
10
src/router/router.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import "vue-router";
|
||||
|
||||
export {};
|
||||
|
||||
declare module "vue-router" {
|
||||
interface RouteMeta {
|
||||
requireAuth?: boolean;
|
||||
onlyGuest?: boolean;
|
||||
}
|
||||
}
|
||||
19
src/stores/App/core.store.ts
Normal file
19
src/stores/App/core.store.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
export const useCore = defineStore("core", {
|
||||
state: () => ({
|
||||
sidebarOpen: true,
|
||||
}),
|
||||
|
||||
getters: {
|
||||
isSidebarOpen(state) {
|
||||
return state.sidebarOpen;
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
setSidebarStatus(value: boolean) {
|
||||
this.sidebarOpen = value;
|
||||
},
|
||||
},
|
||||
});
|
||||
19
src/stores/App/loader.store.ts
Normal file
19
src/stores/App/loader.store.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
export const useLoader = defineStore("loader", {
|
||||
state: () => ({
|
||||
loader: false,
|
||||
}),
|
||||
|
||||
getters: {
|
||||
isLoading(state) {
|
||||
return state.loader;
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
setLoaderStatus(value: boolean) {
|
||||
this.loader = value;
|
||||
},
|
||||
},
|
||||
});
|
||||
68
src/stores/Auth/auth.store.ts
Normal file
68
src/stores/Auth/auth.store.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import api from "@/core/app/api";
|
||||
import { auth, user } from "@/core/app/endpoints";
|
||||
import type { LoginDetails, UserDetails } from "@/core/types/auth/auth.type";
|
||||
import { defineStore } from "pinia";
|
||||
import { useLoader } from "../App/loader.store";
|
||||
import router from "@/router";
|
||||
|
||||
export const useAuth = defineStore("auth", {
|
||||
state: () => ({
|
||||
loginDetails: {} as LoginDetails,
|
||||
isAuthenticated: false,
|
||||
user: {} as UserDetails,
|
||||
}),
|
||||
|
||||
getters: {
|
||||
getLoginDetails(state) {
|
||||
return state.loginDetails;
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
async login() {
|
||||
const loaderStore = useLoader();
|
||||
loaderStore.setLoaderStatus(true);
|
||||
await api
|
||||
.post(auth.login, this.loginDetails)
|
||||
.then(() => {
|
||||
loaderStore.setLoaderStatus(false);
|
||||
this.isAuthenticated = true;
|
||||
router.push({ name: "dashboard" });
|
||||
})
|
||||
.catch(() => {
|
||||
loaderStore.setLoaderStatus(false);
|
||||
this.isAuthenticated = false;
|
||||
});
|
||||
},
|
||||
async getUser() {
|
||||
const loaderStore = useLoader();
|
||||
loaderStore.setLoaderStatus(true);
|
||||
await api
|
||||
.get(user.self)
|
||||
.then((e) => {
|
||||
this.user = e.data.data;
|
||||
loaderStore.setLoaderStatus(false);
|
||||
this.isAuthenticated = true;
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = {} as UserDetails;
|
||||
loaderStore.setLoaderStatus(false);
|
||||
this.isAuthenticated = false;
|
||||
});
|
||||
},
|
||||
|
||||
async logout() {
|
||||
const loaderStore = useLoader();
|
||||
loaderStore.setLoaderStatus(true);
|
||||
await api
|
||||
.post(auth.logout)
|
||||
.then(() => {
|
||||
this.isAuthenticated = false;
|
||||
router.push({ name: "login" });
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
12
src/stores/counter.ts
Normal file
12
src/stores/counter.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { ref, computed } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useCounterStore = defineStore("counter", () => {
|
||||
const count = ref(0);
|
||||
const doubleCount = computed(() => count.value * 2);
|
||||
function increment() {
|
||||
count.value++;
|
||||
}
|
||||
|
||||
return { count, doubleCount, increment };
|
||||
});
|
||||
110
src/views/Auth/Login.vue
Normal file
110
src/views/Auth/Login.vue
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
<template>
|
||||
<div class="login" style="height: 100vh; background-image: url(/svg/large-triangles.svg); background-size: cover">
|
||||
<div class="w-[375px] mx-auto">
|
||||
<div class="flex justify-center">
|
||||
<img src="/img/Dolphin/dolphin-logo.png" class="w-36 my-5" />
|
||||
</div>
|
||||
<div class="border border-[#99A1AF] rounded-xs px-10 py-10 bg-white select-none">
|
||||
<div class="text-center" :class="getLoginError ? '' : 'mb-[10px]'">
|
||||
<p class="text-[30px] text-semibold">Sign In</p>
|
||||
<div class="text-sm font-normal">Fill your detail to sign in to Dolphin PIS.</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-[12px] mb-[-19px]" v-if="getLoginError">
|
||||
<div class="text-sm text-red-700 font-semibold text-center flex gap-[10px]">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-5 -mr-3"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<div>The username or password you entered is incorrect.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="authStore.login" class="pt-10">
|
||||
<div class="">
|
||||
<label for="username">Username</label>
|
||||
<div class="my-1">
|
||||
<input
|
||||
v-model="loginDetails.username"
|
||||
id="username"
|
||||
name="username"
|
||||
type="text"
|
||||
autocomplete="username"
|
||||
required
|
||||
class="p-[10px] max-h-[40px]! h-[40px]! text-[14px] w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-[15px]">
|
||||
<label for="username">Password</label>
|
||||
<div class="my-1 flex">
|
||||
<!--When show password is false-->
|
||||
<input
|
||||
v-model="loginDetails.password"
|
||||
id="password"
|
||||
name="password"
|
||||
:type="!showPassword ? 'password' : 'text'"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
class="p-[10px] max-h-[40px]! h-[40px]! text-[14px] w-full"
|
||||
style="border-radius: 2px 0 0 2px !important"
|
||||
/>
|
||||
<span
|
||||
class="bg-gray-100 flex w-[50px] border-r border-t border-b border-gray-300 hover:bg-gray-200 cursor-pointer transition-all"
|
||||
@click="showPassword = !showPassword"
|
||||
>
|
||||
<div class="m-auto">
|
||||
<Icons
|
||||
name="EyeOff"
|
||||
size="20"
|
||||
class="text-lg text-gray-900 mt-1 mr-[-1px]"
|
||||
v-if="!showPassword"
|
||||
/>
|
||||
<Icons name="Eye" class="text-lg text-gray-900 mt-1" size="20" v-else />
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary w-full max-h-[40px]! h-[40px]! text-lg!"
|
||||
:class="isLoading ? 'btn-loading' : ''"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-center my-5 select-none">
|
||||
<div>
|
||||
© 2024
|
||||
<a href="https://mavorion.com" class="text-[#224CAD] underline">Mavorion Systems</a>
|
||||
. All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useLoader } from "@/stores/App/loader.store";
|
||||
import { useAuth } from "@/stores/Auth/auth.store";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
const authStore = useAuth();
|
||||
const loaderStore = useLoader();
|
||||
const { loginDetails } = storeToRefs(authStore);
|
||||
const { isLoading } = storeToRefs(loaderStore);
|
||||
|
||||
const showPassword = ref(false);
|
||||
|
||||
const getLoginError = "";
|
||||
</script>
|
||||
132
src/views/CSS/Button.vue
Normal file
132
src/views/CSS/Button.vue
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
<template>
|
||||
<div>
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Default Buttons</h1>
|
||||
<div class="flex gap-3.75">
|
||||
<button class="btn btn-primary">Primary</button>
|
||||
<button class="btn btn-secondary">Secondary</button>
|
||||
<button class="btn btn-success">Success</button>
|
||||
<button class="btn btn-warning">Warning</button>
|
||||
<button class="btn btn-danger">Danger</button>
|
||||
<button class="btn btn-info">Info</button>
|
||||
<button class="btn btn-dark">Dark</button>
|
||||
<button class="btn btn-purple">Purple</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Default Disabled Buttons</h1>
|
||||
<div class="flex gap-3.75">
|
||||
<button class="btn btn-primary" disabled>Primary</button>
|
||||
<button class="btn btn-secondary" disabled>Secondary</button>
|
||||
<button class="btn btn-success" disabled>Success</button>
|
||||
<button class="btn btn-warning" disabled>Warning</button>
|
||||
<button class="btn btn-danger" disabled>Danger</button>
|
||||
<button class="btn btn-info" disabled>Info</button>
|
||||
<button class="btn btn-dark" disabled>Dark</button>
|
||||
<button class="btn btn-purple" disabled>Purple</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Outline Buttons</h1>
|
||||
<div class="flex gap-3.75">
|
||||
<button class="btn btn-outline-primary">Primary</button>
|
||||
<button class="btn btn-outline-secondary">Secondary</button>
|
||||
<button class="btn btn-outline-success">Success</button>
|
||||
<button class="btn btn-outline-warning">Warning</button>
|
||||
<button class="btn btn-outline-danger">Danger</button>
|
||||
<button class="btn btn-outline-info">Info</button>
|
||||
<button class="btn btn-outline-dark">Dark</button>
|
||||
<button class="btn btn-outline-purple">Purple</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Outline Disabled Buttons</h1>
|
||||
<div class="flex gap-3.75">
|
||||
<button class="btn btn-outline-primary" disabled>Primary</button>
|
||||
<button class="btn btn-outline-secondary" disabled>Secondary</button>
|
||||
<button class="btn btn-outline-success" disabled>Success</button>
|
||||
<button class="btn btn-outline-warning" disabled>Warning</button>
|
||||
<button class="btn btn-outline-danger" disabled>Danger</button>
|
||||
<button class="btn btn-outline-info" disabled>Info</button>
|
||||
<button class="btn btn-outline-dark" disabled>Dark</button>
|
||||
<button class="btn btn-outline-purple" disabled>Purple</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Ghost Buttons</h1>
|
||||
<div class="flex gap-3.75">
|
||||
<button class="btn btn-ghost-primary">Primary</button>
|
||||
<button class="btn btn-ghost-secondary">Secondary</button>
|
||||
<button class="btn btn-ghost-success">Success</button>
|
||||
<button class="btn btn-ghost-warning">Warning</button>
|
||||
<button class="btn btn-ghost-danger">Danger</button>
|
||||
<button class="btn btn-ghost-info">Info</button>
|
||||
<button class="btn btn-ghost-purple">Purple</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Ghost Disabled Buttons</h1>
|
||||
<div class="flex gap-3.75">
|
||||
<button class="btn btn-ghost-primary" disabled>Primary</button>
|
||||
<button class="btn btn-ghost-secondary" disabled>Secondary</button>
|
||||
<button class="btn btn-ghost-success" disabled>Success</button>
|
||||
<button class="btn btn-ghost-warning" disabled>Warning</button>
|
||||
<button class="btn btn-ghost-danger" disabled>Danger</button>
|
||||
<button class="btn btn-ghost-info" disabled>Info</button>
|
||||
<button class="btn btn-ghost-purple" disabled>Purple</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Button Loading</h1>
|
||||
<div class="flex gap-3.75">
|
||||
<button class="btn btn-primary btn-loading">Primary</button>
|
||||
<button class="btn btn-secondary btn-loading">Secondary</button>
|
||||
<button class="btn btn-success btn-loading">Success</button>
|
||||
<button class="btn btn-warning btn-loading">Warning</button>
|
||||
<button class="btn btn-danger btn-loading">Danger</button>
|
||||
<button class="btn btn-info btn-loading">Info</button>
|
||||
<button class="btn btn-dark btn-loading">Dark</button>
|
||||
<button class="btn btn-purple btn-loading">Purple</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Outline Button Loading</h1>
|
||||
<div class="flex gap-3.75">
|
||||
<button class="btn btn-outline-primary btn-loading">Primary</button>
|
||||
<button class="btn btn-outline-secondary btn-loading">Secondary</button>
|
||||
<button class="btn btn-outline-success btn-loading">Success</button>
|
||||
<button class="btn btn-outline-warning btn-loading">Warning</button>
|
||||
<button class="btn btn-outline-danger btn-loading">Danger</button>
|
||||
<button class="btn btn-outline-info btn-loading">Info</button>
|
||||
<button class="btn btn-outline-purple btn-loading">Purple</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Ghost Button Loading</h1>
|
||||
<div class="flex gap-3.75">
|
||||
<button class="btn btn-ghost-primary btn-loading">Primary</button>
|
||||
<button class="btn btn-ghost-secondary btn-loading">Secondary</button>
|
||||
<button class="btn btn-ghost-success btn-loading">Success</button>
|
||||
<button class="btn btn-ghost-warning btn-loading">Warning</button>
|
||||
<button class="btn btn-ghost-danger btn-loading">Danger</button>
|
||||
<button class="btn btn-ghost-info btn-loading">Info</button>
|
||||
<button class="btn btn-ghost-purple btn-loading">Purple</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Button Group</h1>
|
||||
<div class="flex gap-3.75">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary">First</button>
|
||||
<button class="btn btn-secondary">Second</button>
|
||||
<button class="btn btn-warning">Third</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-outline-secondary">First</button>
|
||||
<button class="btn btn-outline-secondary">Second</button>
|
||||
<button class="btn btn-outline-secondary">Third</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
72
src/views/CSS/Font.vue
Normal file
72
src/views/CSS/Font.vue
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
<template>
|
||||
<div>
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Font Overview</h1>
|
||||
<div class="pl-5">
|
||||
<div>
|
||||
<h2 class="font-bold text-[20px] text-accent-foreground">1) Open Sans (Main Content)</h2>
|
||||
<div class="pl-5">
|
||||
<div>
|
||||
<span class="font-bold">Alphabet (Capital):</span>
|
||||
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-bold">Alphabet (Small):</span>
|
||||
a b c d e f g h i j k l m n o p q r s t u v w x y z
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-bold">Number:</span>
|
||||
1 2 3 4 5 6 7 8 9 0
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-bold">Special Character:</span>
|
||||
~ ``` ! @ # $ % ^ & * () - _ + = {} [] : ; "" '' {{ "<>" }} , . ? / | \
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="font-bold text-[20px] text-accent-foreground">2) Kumbh Sans (Sidebar Content)</h2>
|
||||
<div class="pl-5 font-kumbh">
|
||||
<div>
|
||||
<span class="font-bold">Alphabet (Capital):</span>
|
||||
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-bold">Alphabet (Small):</span>
|
||||
a b c d e f g h i j k l m n o p q r s t u v w x y z
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-bold">Number:</span>
|
||||
1 2 3 4 5 6 7 8 9 0
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-bold">Special Character:</span>
|
||||
~ ``` ! @ # $ % ^ & * () - _ + = {} [] : ; "" '' {{ "<>" }} , . ? / | \
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="font-bold text-[20px] text-accent-foreground">3) Roboto (Login Page)</h2>
|
||||
<div class="pl-5 font-roboto">
|
||||
<div>
|
||||
<span class="font-bold">Alphabet (Capital):</span>
|
||||
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-bold">Alphabet (Small):</span>
|
||||
a b c d e f g h i j k l m n o p q r s t u v w x y z
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-bold">Number:</span>
|
||||
1 2 3 4 5 6 7 8 9 0
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-bold">Special Character:</span>
|
||||
~ ``` ! @ # $ % ^ & * () - _ + = {} [] : ; "" '' {{ "<>" }} , . ? / | \
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
501
src/views/CSS/Input.vue
Normal file
501
src/views/CSS/Input.vue
Normal file
|
|
@ -0,0 +1,501 @@
|
|||
<template>
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold mb-8">Input Component Examples</h1>
|
||||
|
||||
<!-- Basic Inputs -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-2xl font-semibold">Basic Inputs</h2>
|
||||
|
||||
<div class="form-field">
|
||||
<label>Default Input</label>
|
||||
<input type="text" v-model="basicInput" placeholder="Enter text..." class="input-full" />
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>Disabled Input</label>
|
||||
<input type="text" value="Disabled input" disabled class="input-full" />
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>Readonly Input</label>
|
||||
<input type="text" value="Readonly input" readonly class="input-full" />
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>Textarea</label>
|
||||
<textarea v-model="textareaValue" placeholder="Enter multiple lines..." class="input-full"></textarea>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Text Alignment -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-2xl font-semibold">Text Alignment</h2>
|
||||
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="form-field">
|
||||
<label>Left Aligned</label>
|
||||
<input type="text" value="Left" class="input-left input-full" />
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label>Center Aligned</label>
|
||||
<input type="text" value="Center" class="input-center input-full" />
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label>Right Aligned</label>
|
||||
<input type="text" value="Right" class="input-right input-full" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Input States -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-2xl font-semibold">Input States</h2>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="form-field">
|
||||
<label>Error State</label>
|
||||
<input type="text" value="Invalid input" class="input-error input-full" />
|
||||
<span class="field-error">This field is required</span>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label>Success State</label>
|
||||
<input type="text" value="Valid input" class="input-success input-full" />
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label>Warning State</label>
|
||||
<input type="text" value="Warning input" class="input-warning input-full" />
|
||||
<span class="text-xs text-yellow-600">This might cause issues</span>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label>Info State</label>
|
||||
<input type="text" value="Info input" class="input-info input-full" />
|
||||
<span class="field-hint">Additional information here</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Input with Icons -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-2xl font-semibold">Input with Icons</h2>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="form-field">
|
||||
<label>Left Icon</label>
|
||||
<div class="input-icon-wrapper">
|
||||
<input type="text" placeholder="Search..." class="input-full" />
|
||||
<svg class="input-icon-left w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>Right Icon</label>
|
||||
<div class="input-icon-wrapper has-icon-right">
|
||||
<input type="email" v-model="email" placeholder="Email address" class="input-full" />
|
||||
<svg class="input-icon-right w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>Both Icons</label>
|
||||
<div class="input-icon-wrapper has-icon-both">
|
||||
<input type="text" placeholder="Username" class="input-full" />
|
||||
<svg class="input-icon-left w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
||||
></path>
|
||||
</svg>
|
||||
<svg
|
||||
class="input-icon-right input-icon-clickable w-4 h-4"
|
||||
@click="showPassword = !showPassword"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
></path>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>Clearable Input</label>
|
||||
<div class="input-clearable-wrapper">
|
||||
<input
|
||||
type="text"
|
||||
v-model="clearableInput"
|
||||
placeholder="Type something..."
|
||||
class="input-full"
|
||||
/>
|
||||
<svg
|
||||
class="input-clear-btn w-4 h-4"
|
||||
@click="clearableInput = ''"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Input with Buttons -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-2xl font-semibold">Input with Buttons</h2>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="form-field">
|
||||
<label>Right Button</label>
|
||||
<div class="input-side-btn">
|
||||
<input type="text" v-model="searchQuery" placeholder="Search..." class="flex-1" />
|
||||
<button class="btn btn-primary" @click="handleSearch">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>Left Button</label>
|
||||
<div class="input-side-btn-left">
|
||||
<button class="btn btn-secondary">$</button>
|
||||
<input type="number" v-model="price" placeholder="0.00" class="flex-1 input-right" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>Both Buttons</label>
|
||||
<div class="input-both-btn">
|
||||
<button class="btn btn-outline-secondary" @click="decrementCounter">-</button>
|
||||
<input type="number" v-model="counter" class="flex-1 input-center" readonly />
|
||||
<button class="btn btn-outline-secondary" @click="incrementCounter">+</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Input Groups -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-2xl font-semibold">Input Groups</h2>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="form-field">
|
||||
<label>Multiple Inputs</label>
|
||||
<div class="input-group">
|
||||
<input type="text" v-model="firstName" placeholder="First name" class="flex-1" />
|
||||
<input type="text" v-model="lastName" placeholder="Last name" class="flex-1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>With Addons</label>
|
||||
<div class="flex">
|
||||
<span class="input-addon-left">https://</span>
|
||||
<input
|
||||
type="text"
|
||||
v-model="website"
|
||||
placeholder="example.com"
|
||||
class="flex-1 rounded-none border-x-0"
|
||||
/>
|
||||
<span class="input-addon-right">.com</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Floating Label -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-2xl font-semibold">Floating Labels</h2>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="input-floating-wrapper">
|
||||
<input type="text" v-model="floatingName" placeholder=" " class="input-full" />
|
||||
<label>Full Name</label>
|
||||
</div>
|
||||
|
||||
<div class="input-floating-wrapper">
|
||||
<input type="email" v-model="floatingEmail" placeholder=" " class="input-full" />
|
||||
<label>Email Address</label>
|
||||
</div>
|
||||
|
||||
<div class="input-floating-wrapper col-span-2">
|
||||
<textarea v-model="floatingMessage" placeholder=" " class="input-full"></textarea>
|
||||
<label>Your Message</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Special Input Types -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-2xl font-semibold">Special Input Types</h2>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="form-field">
|
||||
<label>Number Input (No Spinner)</label>
|
||||
<input
|
||||
type="number"
|
||||
v-model="numberValue"
|
||||
class="input-no-spinner input-full"
|
||||
placeholder="Enter number"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>Time Input</label>
|
||||
<input type="time" v-model="timeValue" class="input-full" />
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>Color Picker</label>
|
||||
<input type="color" v-model="colorValue" />
|
||||
</div>
|
||||
|
||||
<div class="form-field col-span-2">
|
||||
<label>Range Slider: {{ rangeValue }}</label>
|
||||
<input type="range" v-model="rangeValue" min="0" max="100" class="w-full" />
|
||||
</div>
|
||||
|
||||
<div class="form-field col-span-2">
|
||||
<label>File Upload</label>
|
||||
<input type="file" @change="handleFileUpload" class="input-file input-full" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Checkboxes and Radio -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-2xl font-semibold">Checkboxes & Radio Buttons</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-2 block">Checkbox Sizes</label>
|
||||
<div class="flex items-center gap-4">
|
||||
<label class="flex items-center gap-2">
|
||||
<input type="checkbox" v-model="checkSmall" class="input-sm" />
|
||||
<span class="text-sm">Small</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2">
|
||||
<input type="checkbox" v-model="checkDefault" />
|
||||
<span>Default</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2">
|
||||
<input type="checkbox" v-model="checkLarge" class="input-lg" />
|
||||
<span class="text-lg">Large</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-2 block">Radio Buttons</label>
|
||||
<div class="flex items-center gap-4">
|
||||
<label class="flex items-center gap-2">
|
||||
<input type="radio" v-model="radioValue" value="option1" />
|
||||
<span>Option 1</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2">
|
||||
<input type="radio" v-model="radioValue" value="option2" />
|
||||
<span>Option 2</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2">
|
||||
<input type="radio" v-model="radioValue" value="option3" />
|
||||
<span>Option 3</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Input Variants -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-2xl font-semibold">Input Style Variants</h2>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="form-field">
|
||||
<label>Borderless Input</label>
|
||||
<input type="text" placeholder="No borders..." class="input-borderless input-full bg-gray-50" />
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label>Underline Input</label>
|
||||
<input type="text" placeholder="Underline style..." class="input-underline input-full" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Loading State -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-2xl font-semibold">Loading State</h2>
|
||||
|
||||
<div class="form-field">
|
||||
<label>Loading Input</label>
|
||||
<input type="text" value="Processing..." class="input-loading input-full" readonly />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Form Field with Required -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-2xl font-semibold">Complete Form Field</h2>
|
||||
|
||||
<div class="form-field field-required">
|
||||
<label>Username</label>
|
||||
<input
|
||||
type="text"
|
||||
v-model="username"
|
||||
placeholder="Enter username"
|
||||
class="input-full"
|
||||
:class="{ 'input-error': usernameError }"
|
||||
/>
|
||||
<span v-if="usernameError" class="field-error">{{ usernameError }}</span>
|
||||
<span v-else class="field-hint">Must be at least 3 characters</span>
|
||||
</div>
|
||||
|
||||
<div class="form-field field-required">
|
||||
<label>Password</label>
|
||||
<input
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
v-model="password"
|
||||
placeholder="Enter password"
|
||||
class="input-full"
|
||||
:class="{ 'input-error': passwordError }"
|
||||
/>
|
||||
<span v-if="passwordError" class="field-error">{{ passwordError }}</span>
|
||||
<span v-else class="field-hint">Must contain at least 8 characters</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Form Actions -->
|
||||
<section class="flex gap-4">
|
||||
<button class="btn btn-primary" @click="handleSubmit">Submit Form</button>
|
||||
<button class="btn btn-outline-secondary" @click="handleReset">Reset</button>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
// Basic inputs
|
||||
const basicInput = ref("");
|
||||
const textareaValue = ref("");
|
||||
const email = ref("");
|
||||
const clearableInput = ref("");
|
||||
|
||||
// Search
|
||||
const searchQuery = ref("");
|
||||
|
||||
// Counter
|
||||
const counter = ref(0);
|
||||
const incrementCounter = () => counter.value++;
|
||||
const decrementCounter = () => counter.value--;
|
||||
|
||||
// Price
|
||||
const price = ref("");
|
||||
|
||||
// Input groups
|
||||
const firstName = ref("");
|
||||
const lastName = ref("");
|
||||
const website = ref("");
|
||||
|
||||
// Floating labels
|
||||
const floatingName = ref("");
|
||||
const floatingEmail = ref("");
|
||||
const floatingMessage = ref("");
|
||||
|
||||
// Special inputs
|
||||
const numberValue = ref("");
|
||||
const timeValue = ref("");
|
||||
const colorValue = ref("#3b82f6");
|
||||
const rangeValue = ref(50);
|
||||
|
||||
// Checkboxes
|
||||
const checkSmall = ref(false);
|
||||
const checkDefault = ref(true);
|
||||
const checkLarge = ref(false);
|
||||
const radioValue = ref("option1");
|
||||
|
||||
// Form validation
|
||||
const username = ref("");
|
||||
const password = ref("");
|
||||
const showPassword = ref(false);
|
||||
|
||||
const usernameError = computed(() => {
|
||||
if (username.value && username.value.length < 3) {
|
||||
return "Username must be at least 3 characters";
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
const passwordError = computed(() => {
|
||||
if (password.value && password.value.length < 8) {
|
||||
return "Password must be at least 8 characters";
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
// Handlers
|
||||
const handleSearch = () => {
|
||||
console.log("Searching for:", searchQuery.value);
|
||||
alert(`Searching for: ${searchQuery.value}`);
|
||||
};
|
||||
|
||||
const handleFileUpload = (event: any) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
console.log("File uploaded:", file.name);
|
||||
alert(`File uploaded: ${file.name}`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (usernameError.value || passwordError.value) {
|
||||
alert("Please fix the errors before submitting");
|
||||
return;
|
||||
}
|
||||
console.log("Form submitted:", { username: username.value, password: password.value });
|
||||
alert("Form submitted successfully!");
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
username.value = "";
|
||||
password.value = "";
|
||||
basicInput.value = "";
|
||||
textareaValue.value = "";
|
||||
searchQuery.value = "";
|
||||
counter.value = 0;
|
||||
alert("Form reset!");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Ensure the CSS from the input styles document is imported in your main CSS file */
|
||||
</style>
|
||||
59
src/views/Components/ContentLayout.vue
Normal file
59
src/views/Components/ContentLayout.vue
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<Loader :show="isLoading" />
|
||||
<ContentLayout
|
||||
:title="breadcrumb"
|
||||
:actions="actions"
|
||||
bodyClass="p-4 bg-white"
|
||||
titleClass="justify-between items-center"
|
||||
>
|
||||
<template #body>
|
||||
<p>This is the main content body section.</p>
|
||||
</template>
|
||||
|
||||
<template #action-before>
|
||||
<button class="btn btn-secondary" @click="startLoading">Loader</button>
|
||||
</template>
|
||||
|
||||
<template #action-after>
|
||||
<button class="btn btn-warning" @click="handleAfterAction">After Action</button>
|
||||
</template>
|
||||
</ContentLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
const breadcrumb = ref([
|
||||
{ name: "Home", link: "#" },
|
||||
{ name: "Dashboard", link: "#" },
|
||||
{ name: "Reports", count: 3 },
|
||||
]);
|
||||
|
||||
const actions = ref([
|
||||
{
|
||||
title: "Add Report",
|
||||
emit: "add-report",
|
||||
class: "btn btn-primary",
|
||||
},
|
||||
{
|
||||
title: "Export",
|
||||
emit: "export-data",
|
||||
class: "btn btn-outline-primary",
|
||||
},
|
||||
]);
|
||||
|
||||
const handleAfterAction = () => {
|
||||
console.log("After action clicked");
|
||||
};
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
||||
const startLoading = () => {
|
||||
isLoading.value = true;
|
||||
|
||||
// Simulate async operation
|
||||
setTimeout(() => {
|
||||
isLoading.value = false;
|
||||
}, 3000);
|
||||
};
|
||||
</script>
|
||||
23
src/views/Components/DateRange.vue
Normal file
23
src/views/Components/DateRange.vue
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<DateRange v-model="dateRange" @search="handleSearch" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
const dateRange = ref({
|
||||
startDate: "",
|
||||
endDate: "",
|
||||
dateType: "today",
|
||||
isBS: false,
|
||||
});
|
||||
|
||||
function handleSearch(dateObject: any) {
|
||||
console.log("Searching with dates:", dateObject);
|
||||
fetchReportData(dateObject.startDate, dateObject.endDate);
|
||||
}
|
||||
|
||||
function fetchReportData(startDate: string, endDate: string) {
|
||||
console.log(`Fetching data from ${startDate} to ${endDate}`);
|
||||
}
|
||||
</script>
|
||||
29
src/views/Components/DateSelector.vue
Normal file
29
src/views/Components/DateSelector.vue
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- Basic usage -->
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Basic</h1>
|
||||
<DateSelector v-model="date" />
|
||||
|
||||
<!-- With custom width -->
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Custom Width</h1>
|
||||
<DateSelector v-model="date" classValue="w-full" />
|
||||
|
||||
<!-- Start in BS mode -->
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Default BS</h1>
|
||||
<DateSelector v-model="date" :isBS="true" />
|
||||
|
||||
<!-- Without toggle button -->
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Toggle Removed</h1>
|
||||
<DateSelector v-model="date" :showToggle="false" />
|
||||
|
||||
<!-- Disabled state -->
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Disabled</h1>
|
||||
<DateSelector v-model="date" :disabled="true" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
const date = ref("2025-04-06");
|
||||
</script>
|
||||
19
src/views/Components/ImageCropper.vue
Normal file
19
src/views/Components/ImageCropper.vue
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<template>
|
||||
<button class="btn btn-primary" @click="showModal = true">Open Modal</button>
|
||||
<UploadImage :show="showModal" @onClose="showModal = false" @onCrop="handleCrop" :uploadSize="2" />
|
||||
<img :src="croppedImageVal" alt="" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { UploadImage } from "dolphin-components";
|
||||
import { ref } from "vue";
|
||||
|
||||
const showModal = ref(false);
|
||||
const croppedImageVal = ref("");
|
||||
|
||||
const handleCrop = (croppedImage: string) => {
|
||||
croppedImageVal.value = croppedImage;
|
||||
console.log("Cropped Image:", croppedImage);
|
||||
showModal.value = false;
|
||||
};
|
||||
</script>
|
||||
26
src/views/Components/Loader.vue
Normal file
26
src/views/Components/Loader.vue
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- Your main content -->
|
||||
<button @click="startLoading" class="btn btn-ghost-primary">Load Data</button>
|
||||
|
||||
<!-- Loader component -->
|
||||
<div class="w-full h-[500px]">
|
||||
<Loader :show="isLoading" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
||||
const startLoading = () => {
|
||||
isLoading.value = true;
|
||||
|
||||
// Simulate async operation
|
||||
setTimeout(() => {
|
||||
isLoading.value = false;
|
||||
}, 3000);
|
||||
};
|
||||
</script>
|
||||
35
src/views/Components/Modal.vue
Normal file
35
src/views/Components/Modal.vue
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<template>
|
||||
<div>
|
||||
<button class="btn btn-primary" @click="showModal = true">Open Modal</button>
|
||||
<Modal
|
||||
:title="[{ name: 'Home', link: '/' }, { name: 'Settings' }]"
|
||||
:actions="[
|
||||
{ title: 'Close', emit: 'onClose', class: 'btn btn-outline-primary' },
|
||||
{ title: 'Save', emit: 'saveAction', class: 'btn btn-primary' },
|
||||
]"
|
||||
:show="showModal"
|
||||
width="700px"
|
||||
@onClose="handleClose"
|
||||
@saveAction="handleSave"
|
||||
>
|
||||
<template #default>
|
||||
<p>This is the content of the modal.</p>
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
const showModal = ref(false);
|
||||
|
||||
const handleClose = () => {
|
||||
showModal.value = false;
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
alert("Saving data...");
|
||||
showModal.value = false;
|
||||
};
|
||||
</script>
|
||||
22
src/views/Components/Switch.vue
Normal file
22
src/views/Components/Switch.vue
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<div>
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Default Switch</h1>
|
||||
<Switch v-model="isEnabled" />
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Custom Background Color Switch</h1>
|
||||
<div class="flex gap-[15px]">
|
||||
<Switch v-model="isEnabled" background="bg-black" />
|
||||
<Switch v-model="isEnabled" background="bg-red-500" />
|
||||
<Switch v-model="isEnabled" background="bg-yellow-500" />
|
||||
<Switch v-model="isEnabled" background="bg-green-500" />
|
||||
<Switch v-model="isEnabled" background="bg-primary" />
|
||||
</div>
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Disabled Switch</h1>
|
||||
<Switch v-model="isEnabled" :disable="true" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
const isEnabled = ref(false);
|
||||
</script>
|
||||
1286
src/views/Components/Tabulator.vue
Normal file
1286
src/views/Components/Tabulator.vue
Normal file
File diff suppressed because it is too large
Load Diff
17
src/views/Components/Toggle.vue
Normal file
17
src/views/Components/Toggle.vue
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<div>
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Toggle Button</h1>
|
||||
|
||||
<div class="flex gap-3.75">
|
||||
<Toggle v-model="isToggled" onText="Subscribed" offText="Not Subscribed" width="200px" />
|
||||
<Toggle v-model="isToggled" onText="On" offText="Off" width="60px" />
|
||||
<Toggle v-model="isToggled" onText="BS" offText="AD" width="60px" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
|
||||
const isToggled = ref(false);
|
||||
</script>
|
||||
39
src/views/Components/Towser.vue
Normal file
39
src/views/Components/Towser.vue
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<div class="flex gap-3.75">
|
||||
<button class="btn btn-primary" @click="showTowser = true">Show Towser</button>
|
||||
<button class="btn btn-primary" @click="showTab = true">Show Double Column Towser</button>
|
||||
</div>
|
||||
<Towser
|
||||
:show="showTowser"
|
||||
:title="[{ name: 'Home', link: '/' }, { name: 'Settings' }]"
|
||||
:actions="[{ title: 'Close', emit: 'onClose', class: 'btn btn-outline-primary' }]"
|
||||
@onClose="showTowser = false"
|
||||
>
|
||||
<p>This is the content of the modal.</p>
|
||||
</Towser>
|
||||
|
||||
<Towser
|
||||
:show="showTab"
|
||||
:double="true"
|
||||
:title="[{ name: 'Dashboard' }, { name: 'Details' }]"
|
||||
:actions="[
|
||||
{ title: 'Cancel', emit: 'onClose', class: 'btn btn-outline-primary' },
|
||||
{ title: 'Save', emit: 'saveAction', class: 'btn btn-primary' },
|
||||
]"
|
||||
@onClose="showTab = false"
|
||||
>
|
||||
<template #default>
|
||||
<p>This is the main content.</p>
|
||||
</template>
|
||||
<template #body2>
|
||||
<p>This is the second column.</p>
|
||||
</template>
|
||||
</Towser>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
|
||||
const showTowser = ref(false);
|
||||
const showTab = ref(false);
|
||||
</script>
|
||||
42
src/views/Components/Wizard.vue
Normal file
42
src/views/Components/Wizard.vue
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div class="w-[900px] mx-auto mt-[100px]">
|
||||
<Wizard
|
||||
:total-steps="5"
|
||||
:current-step="currentStep"
|
||||
:step-labels="['Setup', 'Config', 'Review', 'Deploy', 'Done']"
|
||||
@step-change="handleLabeledStepChange"
|
||||
/>
|
||||
<div class="mt-4 flex gap-2">
|
||||
<button @click="prevLabeledStep" :disabled="currentStep <= 1" class="btn btn-outline-primary">
|
||||
Previous
|
||||
</button>
|
||||
<button @click="nextLabeledStep" :disabled="currentStep >= 5" class="btn btn-outline-primary">Next</button>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-600">
|
||||
Current Step: {{ currentStep }} -
|
||||
{{ ["Setup", "Config", "Review", "Deploy", "Done"][currentStep - 1] }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
const currentStep = ref(1);
|
||||
|
||||
const handleLabeledStepChange = (step: number) => {
|
||||
currentStep.value = step;
|
||||
};
|
||||
|
||||
const nextLabeledStep = () => {
|
||||
if (currentStep.value < 5) {
|
||||
currentStep.value++;
|
||||
}
|
||||
};
|
||||
|
||||
const prevLabeledStep = () => {
|
||||
if (currentStep.value > 1) {
|
||||
currentStep.value--;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
5
src/views/Dashboard/Index.vue
Normal file
5
src/views/Dashboard/Index.vue
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<div>test</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
32
src/views/Directives/InputCurrency.vue
Normal file
32
src/views/Directives/InputCurrency.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">English (US Decimal) - ({{ intValue1 }})</h1>
|
||||
<input type="text" v-input-currency:foreign v-input-select v-model.number="intValue1" id="input1" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Nepali (US Decimal) - ({{ intValue2 }})</h1>
|
||||
<input type="text" v-input-currency v-input-select v-model.number="intValue2" id="input2" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">English (Euro Decimal) - ({{ intValue3 }})</h1>
|
||||
<input type="text" v-input-currency.euro v-input-select v-model.number="intValue3" id="input3" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1 class="font-bold text-[25px] text-accent-foreground">Nepali (Euro Decimal) - ({{ intValue4 }})</h1>
|
||||
<input type="text" v-input-currency:foreign.euro v-input-select v-model.number="intValue4" id="input4" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
const intValue1 = ref(1210034.56);
|
||||
const intValue2 = ref(7180090.12);
|
||||
const intValue3 = ref(3400156.78);
|
||||
const intValue4 = ref(9100012.34);
|
||||
</script>
|
||||
14
src/views/Directives/InputError.vue
Normal file
14
src/views/Directives/InputError.vue
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<div class="form-field" v-input-error="errorMessage">
|
||||
<label>Username</label>
|
||||
<input v-model="username" type="text" placeholder="Enter username" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
const username = ref("");
|
||||
const hasError = computed(() => username.value.length < 3);
|
||||
const errorMessage = computed(() => (hasError.value ? "Username must be at least 3 characters" : ""));
|
||||
</script>
|
||||
16
src/views/Directives/InputSelect.vue
Normal file
16
src/views/Directives/InputSelect.vue
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<template>
|
||||
<div class="w-full">
|
||||
<input v-input-select type="text" v-model="name" class="w-full" />
|
||||
</div>
|
||||
<br />
|
||||
<div class="w-full">
|
||||
<textarea v-input-select v-model="description" class="w-full"></textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
const name = ref("John Doe");
|
||||
const description = ref("Enter your description here");
|
||||
</script>
|
||||
34
src/views/Directives/Tooltip.vue
Normal file
34
src/views/Directives/Tooltip.vue
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
//HMTL Element Load Example
|
||||
<template>
|
||||
<button v-tooltip="{ content: 'Save changes', preferredPosition: 'right' }">Save</button>
|
||||
<button
|
||||
v-tooltip="{
|
||||
content: tooltipHtml,
|
||||
raw: true,
|
||||
preferredPosition: 'right',
|
||||
}"
|
||||
class="btn"
|
||||
>
|
||||
Hover Me
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
const tooltipHtml = ref("");
|
||||
|
||||
onMounted(() => {
|
||||
tooltipHtml.value = `
|
||||
<div>
|
||||
<strong>Tooltip Title</strong><br/>
|
||||
<em>Some italic text</em><br/>
|
||||
<ul>
|
||||
<li>Item One</li>
|
||||
<li>Item Two</li>
|
||||
<li>Item Three</li>
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
</script>
|
||||
12
tsconfig.app.json
Normal file
12
tsconfig.app.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["src/**/*.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
11
tsconfig.json
Normal file
11
tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
tsconfig.node.json
Normal file
19
tsconfig.node.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "@tsconfig/node24/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*",
|
||||
"eslint.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
21
vite.config.ts
Normal file
21
vite.config.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { fileURLToPath, URL } from "node:url";
|
||||
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import vueDevTools from "vite-plugin-vue-devtools";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
export default defineConfig({
|
||||
define: {
|
||||
global: "window",
|
||||
},
|
||||
plugins: [vue(), vueDevTools(), tailwindcss()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
},
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user