Compare commits
7 Commits
a4eb3b52cf
...
ee375b7cbd
| Author | SHA1 | Date | |
|---|---|---|---|
| ee375b7cbd | |||
| c20cdf2b63 | |||
| 2ce2556685 | |||
| e961967a49 | |||
| 7c69d56a48 | |||
| 1494af985a | |||
| 9513feba37 |
30
bo/bun.lock
30
bo/bun.lock
@@ -1,25 +1,25 @@
|
|||||||
{
|
{
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"configVersion": 0,
|
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "bo",
|
"name": "bo",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/ui": "^4.5.0",
|
"@nuxt/ui": "^4.6.0",
|
||||||
"@tailwindcss/vite": "^4.2.0",
|
"@tailwindcss/vite": "^4.2.2",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"tailwindcss": "^4.2.0",
|
"reka-ui": "^2.9.3",
|
||||||
|
"tailwindcss": "^4.2.2",
|
||||||
"vue": "beta",
|
"vue": "beta",
|
||||||
"vue-chartjs": "^5.3.3",
|
"vue-chartjs": "^5.3.3",
|
||||||
"vue-i18n": "11",
|
"vue-i18n": "^11.3.0",
|
||||||
"vue-router": "^5.0.2",
|
"vue-router": "^5.0.4",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tsconfig/node24": "^24.0.4",
|
"@tsconfig/node24": "^24.0.4",
|
||||||
"@types/node": "^24.10.13",
|
"@types/node": "^24.12.0",
|
||||||
"@vitejs/plugin-vue": "^6.0.4",
|
"@vitejs/plugin-vue": "^6.0.5",
|
||||||
"@vue/eslint-config-typescript": "^14.6.0",
|
"@vue/eslint-config-typescript": "^14.7.0",
|
||||||
"@vue/tsconfig": "^0.8.1",
|
"@vue/tsconfig": "^0.8.1",
|
||||||
"jiti": "^2.6.1",
|
"jiti": "^2.6.1",
|
||||||
"npm-run-all2": "^8.0.4",
|
"npm-run-all2": "^8.0.4",
|
||||||
@@ -27,8 +27,8 @@
|
|||||||
"prettier": "3.8.1",
|
"prettier": "3.8.1",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "beta",
|
"vite": "beta",
|
||||||
"vite-plugin-vue-devtools": "^8.0.6",
|
"vite-plugin-vue-devtools": "^8.1.1",
|
||||||
"vue-tsc": "^3.2.4",
|
"vue-tsc": "^3.2.6",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1012,7 +1012,7 @@
|
|||||||
|
|
||||||
"regexp-tree": ["regexp-tree@0.1.27", "", { "bin": { "regexp-tree": "bin/regexp-tree" } }, "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA=="],
|
"regexp-tree": ["regexp-tree@0.1.27", "", { "bin": { "regexp-tree": "bin/regexp-tree" } }, "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA=="],
|
||||||
|
|
||||||
"reka-ui": ["reka-ui@2.9.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.13", "@floating-ui/vue": "^1.1.6", "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", "@vueuse/core": "^14.1.0", "@vueuse/shared": "^14.1.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^2.0.11" }, "peerDependencies": { "vue": ">= 3.4.0" } }, "sha512-/t4e6y1hcG+uDuRfpg6tbMz3uUEvRzNco6NeYTufoJeUghy5Iosxos5YL/p+ieAsid84sdMX9OrgDqpEuCJhBw=="],
|
"reka-ui": ["reka-ui@2.9.3", "", { "dependencies": { "@floating-ui/dom": "^1.6.13", "@floating-ui/vue": "^1.1.6", "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", "@vueuse/core": "^14.1.0", "@vueuse/shared": "^14.1.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^2.0.11" }, "peerDependencies": { "vue": ">= 3.4.0" } }, "sha512-C9lCVxsSC7uYD0Nbgik1+14FNndHNprZmf0zGQt0ZDYIt5KxXV3zD0hEqNcfRUsEEJvVmoRsUkJnASBVBeaaUw=="],
|
||||||
|
|
||||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||||
|
|
||||||
@@ -1186,6 +1186,8 @@
|
|||||||
|
|
||||||
"@nuxt/schema/std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="],
|
"@nuxt/schema/std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="],
|
||||||
|
|
||||||
|
"@nuxt/ui/reka-ui": ["reka-ui@2.9.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.13", "@floating-ui/vue": "^1.1.6", "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", "@vueuse/core": "^14.1.0", "@vueuse/shared": "^14.1.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^2.0.11" }, "peerDependencies": { "vue": ">= 3.4.0" } }, "sha512-/t4e6y1hcG+uDuRfpg6tbMz3uUEvRzNco6NeYTufoJeUghy5Iosxos5YL/p+ieAsid84sdMX9OrgDqpEuCJhBw=="],
|
||||||
|
|
||||||
"@nuxtjs/color-mode/@nuxt/kit": ["@nuxt/kit@3.21.2", "", { "dependencies": { "c12": "^3.3.3", "consola": "^3.4.2", "defu": "^6.1.4", "destr": "^2.0.5", "errx": "^0.1.0", "exsolve": "^1.0.8", "ignore": "^7.0.5", "jiti": "^2.6.1", "klona": "^2.0.6", "knitwork": "^1.3.0", "mlly": "^1.8.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "rc9": "^3.0.0", "scule": "^1.3.0", "semver": "^7.7.4", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unctx": "^2.5.0", "untyped": "^2.0.0" } }, "sha512-Bd6m6mrDrqpBEbX+g0rc66/ALd1sxlgdx5nfK9MAYO0yKLTOSK7McSYz1KcOYn3LQFCXOWfvXwaqih/b+REI1g=="],
|
"@nuxtjs/color-mode/@nuxt/kit": ["@nuxt/kit@3.21.2", "", { "dependencies": { "c12": "^3.3.3", "consola": "^3.4.2", "defu": "^6.1.4", "destr": "^2.0.5", "errx": "^0.1.0", "exsolve": "^1.0.8", "ignore": "^7.0.5", "jiti": "^2.6.1", "klona": "^2.0.6", "knitwork": "^1.3.0", "mlly": "^1.8.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "rc9": "^3.0.0", "scule": "^1.3.0", "semver": "^7.7.4", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unctx": "^2.5.0", "untyped": "^2.0.0" } }, "sha512-Bd6m6mrDrqpBEbX+g0rc66/ALd1sxlgdx5nfK9MAYO0yKLTOSK7McSYz1KcOYn3LQFCXOWfvXwaqih/b+REI1g=="],
|
||||||
|
|
||||||
"@nuxtjs/color-mode/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
|
"@nuxtjs/color-mode/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
|
||||||
@@ -1254,6 +1256,8 @@
|
|||||||
|
|
||||||
"vaul-vue/@vueuse/core": ["@vueuse/core@10.11.1", "", { "dependencies": { "@types/web-bluetooth": "^0.0.20", "@vueuse/metadata": "10.11.1", "@vueuse/shared": "10.11.1", "vue-demi": ">=0.14.8" } }, "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww=="],
|
"vaul-vue/@vueuse/core": ["@vueuse/core@10.11.1", "", { "dependencies": { "@types/web-bluetooth": "^0.0.20", "@vueuse/metadata": "10.11.1", "@vueuse/shared": "10.11.1", "vue-demi": ">=0.14.8" } }, "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww=="],
|
||||||
|
|
||||||
|
"vaul-vue/reka-ui": ["reka-ui@2.9.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.13", "@floating-ui/vue": "^1.1.6", "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", "@vueuse/core": "^14.1.0", "@vueuse/shared": "^14.1.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^2.0.11" }, "peerDependencies": { "vue": ">= 3.4.0" } }, "sha512-/t4e6y1hcG+uDuRfpg6tbMz3uUEvRzNco6NeYTufoJeUghy5Iosxos5YL/p+ieAsid84sdMX9OrgDqpEuCJhBw=="],
|
||||||
|
|
||||||
"vue-i18n/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
|
"vue-i18n/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
|
||||||
|
|
||||||
"vue-router/@vue/devtools-api": ["@vue/devtools-api@8.1.1", "", { "dependencies": { "@vue/devtools-kit": "^8.1.1" } }, "sha512-bsDMJ07b3GN1puVwJb/fyFnj/U2imyswK5UQVLZwVl7O05jDrt6BHxeG5XffmOOdasOj/bOmIjxJvGPxU7pcqw=="],
|
"vue-router/@vue/devtools-api": ["@vue/devtools-api@8.1.1", "", { "dependencies": { "@vue/devtools-kit": "^8.1.1" } }, "sha512-bsDMJ07b3GN1puVwJb/fyFnj/U2imyswK5UQVLZwVl7O05jDrt6BHxeG5XffmOOdasOj/bOmIjxJvGPxU7pcqw=="],
|
||||||
@@ -1283,5 +1287,7 @@
|
|||||||
"vaul-vue/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@10.11.1", "", {}, "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw=="],
|
"vaul-vue/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@10.11.1", "", {}, "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw=="],
|
||||||
|
|
||||||
"vaul-vue/@vueuse/core/@vueuse/shared": ["@vueuse/shared@10.11.1", "", { "dependencies": { "vue-demi": ">=0.14.8" } }, "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA=="],
|
"vaul-vue/@vueuse/core/@vueuse/shared": ["@vueuse/shared@10.11.1", "", { "dependencies": { "vue-demi": ">=0.14.8" } }, "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA=="],
|
||||||
|
|
||||||
|
"vaul-vue/reka-ui/@vueuse/core": ["@vueuse/core@14.2.1", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.2.1", "@vueuse/shared": "14.2.1" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
bo/components.d.ts
vendored
3
bo/components.d.ts
vendored
@@ -46,6 +46,8 @@ declare module 'vue' {
|
|||||||
UCheckbox: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Checkbox.vue')['default']
|
UCheckbox: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Checkbox.vue')['default']
|
||||||
UDrawer: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Drawer.vue')['default']
|
UDrawer: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Drawer.vue')['default']
|
||||||
UDropdownMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/DropdownMenu.vue')['default']
|
UDropdownMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/DropdownMenu.vue')['default']
|
||||||
|
UEditor: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Editor.vue')['default']
|
||||||
|
UEditorToolbar: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/EditorToolbar.vue')['default']
|
||||||
UForm: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Form.vue')['default']
|
UForm: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Form.vue')['default']
|
||||||
UFormField: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/FormField.vue')['default']
|
UFormField: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/FormField.vue')['default']
|
||||||
UIcon: typeof import('./node_modules/@nuxt/ui/dist/runtime/vue/components/Icon.vue')['default']
|
UIcon: typeof import('./node_modules/@nuxt/ui/dist/runtime/vue/components/Icon.vue')['default']
|
||||||
@@ -58,5 +60,6 @@ declare module 'vue' {
|
|||||||
USelectMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/SelectMenu.vue')['default']
|
USelectMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/SelectMenu.vue')['default']
|
||||||
USidebar: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Sidebar.vue')['default']
|
USidebar: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Sidebar.vue')['default']
|
||||||
UTable: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Table.vue')['default']
|
UTable: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Table.vue')['default']
|
||||||
|
UTabs: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Tabs.vue')['default']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"@tailwindcss/vite": "^4.2.2",
|
"@tailwindcss/vite": "^4.2.2",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
|
"reka-ui": "^2.9.3",
|
||||||
"tailwindcss": "^4.2.2",
|
"tailwindcss": "^4.2.2",
|
||||||
"vue": "beta",
|
"vue": "beta",
|
||||||
"vue-chartjs": "^5.3.3",
|
"vue-chartjs": "^5.3.3",
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { TooltipProvider } from 'reka-ui'
|
||||||
import { RouterView } from 'vue-router'
|
import { RouterView } from 'vue-router'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<RouterView />
|
<TooltipProvider>
|
||||||
|
<RouterView />
|
||||||
|
</TooltipProvider>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,6 @@ export const uiOptions: NuxtUIOptions = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
// slots: {
|
|
||||||
// base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
|
|
||||||
// },
|
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
slots: {
|
slots: {
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ body {
|
|||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--main-light: #FFFEFB;
|
--main-light: #FFFEFB;
|
||||||
--second-light: #F5F6FA;
|
--second-light: #F8FAFC;
|
||||||
|
|
||||||
--main-dark: #212121;
|
--main-dark: #1A1A1A;
|
||||||
--black: #1A1A1A;
|
--black: #1A1A1A;
|
||||||
|
|
||||||
/* gray */
|
/* gray */
|
||||||
@@ -25,13 +25,15 @@ body {
|
|||||||
--gray-dark: #6B7280;
|
--gray-dark: #6B7280;
|
||||||
|
|
||||||
/* borders */
|
/* borders */
|
||||||
--border-light: #E8E7E0;
|
--border-light: #E2E8F0;
|
||||||
--border-dark: #3F3E3D;
|
--border-dark: #334155;
|
||||||
|
|
||||||
/* text */
|
/* text */
|
||||||
--accent-blue-dark: #3B82F6;
|
--accent-blue-dark: #0EA5E91A;
|
||||||
--accent-blue-light: #2563EB;
|
--accent-blue-light: #E0F2FE;
|
||||||
--text-dark: #FFFEFB;
|
--text-dark: #FFFEFB;
|
||||||
|
--text-sky-light: #0284C7;
|
||||||
|
--text-sky-dark: #38BDF8;
|
||||||
|
|
||||||
/* placeholder */
|
/* placeholder */
|
||||||
--placeholder: #8C8C8A;
|
--placeholder: #8C8C8A;
|
||||||
@@ -53,6 +55,7 @@ body {
|
|||||||
--ui-error: var(--dark-red);
|
--ui-error: var(--dark-red);
|
||||||
--border: var(--border-dark);
|
--border: var(--border-dark);
|
||||||
--tw-border-style: var(--border-dark);
|
--tw-border-style: var(--border-dark);
|
||||||
|
--ui-text-inverted: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-form {
|
.label-form {
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="flex gap-10">
|
<div class="flex flex-col md:flex-row gap-10">
|
||||||
<CategoryMenu />
|
<CategoryMenu />
|
||||||
<div class="w-full flex flex-col items-center gap-4">
|
<div class="w-full flex flex-col items-center gap-4">
|
||||||
<UTable :data="productsList" :columns="columns" class="flex-1 w-full" />
|
<UTable :data="productsList" :columns="columns" class="flex-1 w-full" :ui="{
|
||||||
|
root : 'max-w-100wv overflow-auto!'
|
||||||
|
}" />
|
||||||
<UPagination v-model:page="page" :total="total" :items-per-page="perPage" />
|
<UPagination v-model:page="page" :total="total" :items-per-page="perPage" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,67 +1,93 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<p class="cursor-pointer" @click="backFromProduct()">Back to products</p>
|
<div class="flex items-center gap-2 mb-4">
|
||||||
<div class="container my-10 mx-auto ">
|
<UIcon name="line-md:arrow-left" class="text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
||||||
|
<p class="cursor-pointer text-(--text-sky-light) dark:text-(--text-sky-dark)" @click="backFromProduct()">
|
||||||
|
Back to products</p>
|
||||||
|
</div>
|
||||||
|
<div class="container mx-auto ">
|
||||||
<div
|
<div
|
||||||
class=" gap-4 mb-6 bg-(--second-light) dark:bg-(--main-dark) border border-(--border-light) dark:border-(--border-dark) p-4 rounded-md">
|
class="gap-4 mb-6 bg-slate-50 dark:bg-(--main-dark) border border-(--border-light) dark:border-(--border-dark) p-4 rounded-md">
|
||||||
<div v-if="!isShowProductView" class="flex items-end justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center gap-3" v-if="!isTranslations">
|
<div class="flex flex-col items-center gap-3" v-if="!isTranslations">
|
||||||
<p class="text-red-500 text-md whitespace-nowrap">Translate from Polish to</p>
|
<USelectMenu v-model="toLangId" :items="langs" value-key="id"
|
||||||
<USelect v-model="toLangId" :items="availableLangs" value-key="id" label-key="name"
|
class="w-48 bg-(--main-light) dark:bg-(--black) rounded-md shadow-sm" :searchInput="false">
|
||||||
placeholder="Select language" class="w-48">
|
<template #default>
|
||||||
<template #selected="{ modelValue }">
|
<div class="flex flex-col items-start leading-tight">
|
||||||
{{ modelValue }}
|
<span class="text-xs text-gray-400">
|
||||||
</template>
|
Selected language
|
||||||
<template #item-label="{ item }">
|
</span>
|
||||||
<div class="flex items-center gap-2">
|
<span class="font-medium dark:text-white text-black">
|
||||||
<span>{{ item.flag }}</span>
|
{{langs.find(l => l.id === toLangId)?.name || 'Select language'}}
|
||||||
<span>{{ item.name }}</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</USelect>
|
|
||||||
|
<template #item-leading="{ item }">
|
||||||
|
<div class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<span class="text-lg">{{ item.flag }}</span>
|
||||||
|
<span class="font-medium dark:text-white text-black">
|
||||||
|
{{ item.name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</USelectMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-3 items-end justify-end">
|
<div v-if="toLangId !== settingStore.shopDefaultLanguage && !isTranslations" class="flex gap-7">
|
||||||
|
<UButton @click="translateToSelectedLanguage" color="info" :loading="translating">
|
||||||
|
Translate from Polish to {{langs.find(l => l.id === toLangId)?.name}}
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
<div v-if="isTranslations" class="flex gap-3 w-full justify-end">
|
||||||
<UButton @click="() => {
|
<UButton @click="() => {
|
||||||
fetchForLanguage(toLangId)
|
toLangId = settingStore.shopDefaultLanguage
|
||||||
isShowProductView = true
|
isTranslations = false
|
||||||
}" color="primary" v-if="isTranslations === false"
|
}" color="info" variant="outline">
|
||||||
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) px-12!">
|
Cancel and back to Polish
|
||||||
Show product
|
|
||||||
</UButton>
|
</UButton>
|
||||||
<UButton @click="translateToSelectedLanguage" color="primary" :loading="translating"
|
<UButton color="info" @click="productStore.saveProductDescription(productID, toLangId)">
|
||||||
v-if="isTranslations === false"
|
Save translations
|
||||||
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) px-12!">
|
|
||||||
Translate
|
|
||||||
</UButton>
|
</UButton>
|
||||||
<div v-else class="flex gap-3 items-end justify-end">
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- <div class="flex gap-7">
|
||||||
|
<div v-if="isTranslations === false">
|
||||||
|
<UButton @click="() => {
|
||||||
|
fetchForLanguage(toLangId)
|
||||||
|
isShowProductView = true
|
||||||
|
}" class="text-(--accent-blue-light) dark:text-(--accent-blue-dark)"
|
||||||
|
color="neutral" variant="outline">
|
||||||
|
<p class="dark:text-white"> Show product</p>
|
||||||
|
</UButton>
|
||||||
|
<UButton @click="translateToSelectedLanguage" color="primary" :loading="translating"
|
||||||
|
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) flex items-center! justify-center! px-12">
|
||||||
|
Translate
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex gap-3">
|
||||||
<UButton @click="() => {
|
<UButton @click="() => {
|
||||||
toLangId = settingStore.shopDefaultLanguage
|
toLangId = settingStore.shopDefaultLanguage
|
||||||
isTranslations = false
|
isTranslations = false
|
||||||
}" color="primary" class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) px-12!">
|
}" color="primary" class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark)">
|
||||||
Cancel
|
Cancel
|
||||||
</UButton>
|
</UButton>
|
||||||
<UButton color="primary" @click="productStore.saveProductDescription(productID, toLangId)"
|
<UButton color="primary" @click="productStore.saveProductDescription(productID, toLangId)"
|
||||||
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) px-12!">
|
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark)">
|
||||||
Save
|
Save
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<UButton @click="isShowProductView = false" color="primary"
|
|
||||||
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) px-12!">
|
|
||||||
Back
|
|
||||||
</UButton>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="translating" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
<!-- <div v-if="translating" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||||
<div class="flex flex-col items-center gap-4 p-8 bg-(--main-light) dark:bg-(--main-dark) rounded-lg shadow-xl">
|
<div class="flex flex-col items-center gap-4 p-8 bg-(--main-light) dark:bg-(--main-dark) rounded-lg shadow-xl">
|
||||||
<UIcon name="svg-spinners:ring-resize" class="text-4xl text-primary" />
|
<UIcon name="svg-spinners:ring-resize" class="text-4xl text-primary" />
|
||||||
<p class="text-lg font-medium dark:text-white text-black">Translating...</p>
|
<p class="text-lg font-medium dark:text-white text-black">Translating...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<div v-if="productStore.loading" class="flex items-center justify-center py-20">
|
<div v-if="productStore.loading" class="flex items-center justify-center py-20">
|
||||||
<UIcon name="svg-spinners:ring-resize" class="text-4xl text-primary" />
|
<UIcon name="svg-spinners:ring-resize" class="text-4xl text-primary" />
|
||||||
@@ -70,87 +96,96 @@
|
|||||||
<p class="text-red-500">{{ productStore.error }}</p>
|
<p class="text-red-500">{{ productStore.error }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="productStore.productDescription" class="flex items-start gap-30">
|
<div v-else-if="productStore.productDescription" class="">
|
||||||
<div class="w-80 h-80 bg-(--second-light) dark:bg-gray-700 rounded-lg flex items-center justify-center">
|
<div class="flex items-start gap-30">
|
||||||
<span class="text-gray-500 dark:text-gray-400">Product Image</span>
|
<div class="w-80 h-80 bg-(--second-light) dark:bg-gray-700 rounded-lg flex items-center justify-center">
|
||||||
</div>
|
<span class="text-gray-500 dark:text-gray-400">Product Image</span>
|
||||||
<div class="flex flex-col gap-2">
|
</div>
|
||||||
<p class="text-[25px] font-bold text-black dark:text-white">
|
<div class="flex flex-col gap-2">
|
||||||
{{ productStore.productDescription.name || 'Product Name' }}
|
<p class="text-[25px] font-bold text-black dark:text-white">
|
||||||
</p>
|
{{ productStore.productDescription.name || 'Product Name' }}
|
||||||
<p v-html="productStore.productDescription.description_short" class="text-black dark:text-white"></p>
|
</p>
|
||||||
<div class="space-y-[10px]">
|
<p v-html="productStore.productDescription.description_short" class="text-black dark:text-white"></p>
|
||||||
<div class="flex items-center gap-1">
|
|
||||||
<UIcon name="lets-icons:done-ring-round-fill" class="text-[20px] text-green-600" />
|
<div class="space-y-[10px]">
|
||||||
<p class="text-[16px] font-bold text-(--accent-blue-light) dark:text-(--accent-blue-dark)">
|
<div class="flex items-center gap-1">
|
||||||
{{ productStore.productDescription.available_now }}
|
<UIcon name="lets-icons:done-ring-round-fill"
|
||||||
</p>
|
class="text-[20px] light:text-(--accent-green-light) dark:text-(--accent-green-dark)" />
|
||||||
</div>
|
<p class="text-[16px] font-bold text-(--accent-blue-light) dark:text-(--accent-blue-dark)">
|
||||||
<div class="flex items-center gap-1">
|
{{ productStore.productDescription.available_now }}
|
||||||
<UIcon name="marketeq:car-shipping" class="text-[25px] text-green-600" />
|
</p>
|
||||||
<p class="text-[18px] font-bold text-black dark:text-white">
|
</div>
|
||||||
{{ productStore.productDescription.delivery_in_stock || 'Delivery information' }}
|
<div class="flex items-center gap-1">
|
||||||
</p>
|
<UIcon name="marketeq:car-shipping"
|
||||||
|
class="text-[25px] light:text-(--accent-green-light) dark:text-(--accent-green-dark)" />
|
||||||
|
<p class="text-[18px] font-bold text-black dark:text-white">
|
||||||
|
{{ productStore.productDescription.delivery_in_stock || 'Delivery information' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="productStore.productDescription" class="mt-16">
|
<UTabs :items="items" v-model="activeTab" color="info" :content="true">
|
||||||
<div class="flex gap-4 my-6">
|
<template #description>
|
||||||
<UButton @click="activeTab = 'description'"
|
<div v-if="!isTranslations" class="flex justify-end items-center gap-3 mb-4">
|
||||||
:class="['cursor-pointer', activeTab === 'description' ? 'bg-blue-500 text-white' : '']" color="neutral"
|
<UButton v-if="!isEditing" @click="activeTab === 'usage' ? enableEdit() : enableDescriptionEdit()"
|
||||||
variant="outline">
|
class="flex items-center gap-2 m-2 cursor-pointer bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)!">
|
||||||
<p class="dark:text-white">Description</p>
|
<p class="text-white">Change Text</p>
|
||||||
</UButton>
|
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-[30px] text-white!" />
|
||||||
|
</UButton>
|
||||||
|
<UButton v-if="isEditing" @click="activeTab === 'usage' ? saveText : saveDescription" color="neutral"
|
||||||
|
variant="outline" class="p-2.5 cursor-pointer">
|
||||||
|
<p class="dark:text-white text-black">Save the edited text</p>
|
||||||
|
</UButton>
|
||||||
|
<UButton v-if="isEditing" @click="activeTab === 'usage' ? cancelEdit : cancelDescriptionEdit"
|
||||||
|
color="neutral" variant="outline" class="p-2.5 cursor-pointer">
|
||||||
|
Cancel
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
<UEditor v-if="isTranslations" v-slot="{ editor }" v-model="productStore.productDescription.description"
|
||||||
|
content-type="html" :ui="{ base: 'p-8 sm:px-16' }" class="w-full min-h-74" placeholder="Write there ...">
|
||||||
|
<UEditorToolbar :editor="editor" :items="toolbarItems" class="sm:px-8">
|
||||||
|
<template #link>
|
||||||
|
<EditorLinkPopover :editor="editor" auto-open />
|
||||||
|
</template>
|
||||||
|
</UEditorToolbar>
|
||||||
|
</UEditor>
|
||||||
|
<p v-else v-html="productStore.productDescription.description" class="text-black dark:text-white"></p>
|
||||||
|
</template>
|
||||||
|
|
||||||
<UButton @click="activeTab = 'usage'"
|
<template #usage>
|
||||||
:class="['cursor-pointer', activeTab === 'usage' ? 'bg-blue-500 text-white' : '']" color="neutral"
|
<div class="px-7">
|
||||||
variant="outline">
|
<div class="flex justify-end items-center gap-3 mb-4">
|
||||||
<p class="dark:text-white">Usage</p>
|
<UButton v-if="!isEditing" @click="enableEdit"
|
||||||
</UButton>
|
class="flex items-center gap-2 m-2 cursor-pointer bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)!">
|
||||||
</div>
|
<p class="text-white">Change Text</p>
|
||||||
|
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-[30px] text-white!" />
|
||||||
|
</UButton>
|
||||||
|
|
||||||
<div v-if="activeTab === 'usage'"
|
<UButton v-if="isEditing" @click="saveText" color="neutral" variant="outline"
|
||||||
class="px-8 py-4 border border-(--border-light) dark:border-(--border-dark) rounded-md bg-(--second-light) dark:bg-(--main-dark)">
|
class="p-2.5 cursor-pointer">
|
||||||
|
<p class="dark:text-white text-black">Save the edited text</p>
|
||||||
|
</UButton>
|
||||||
|
|
||||||
<div class="flex justify-end items-center gap-3 mb-4">
|
<UButton v-if="isEditing" @click="cancelEdit" color="neutral" variant="outline"
|
||||||
<UButton v-if="!isEditing" @click="enableEdit"
|
class="p-2.5 cursor-pointer">
|
||||||
class="flex items-center gap-2 m-2 cursor-pointer bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)!">
|
Cancel
|
||||||
<p class="text-white">Change Text</p>
|
</UButton>
|
||||||
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-[30px] text-white!" />
|
</div>
|
||||||
</UButton>
|
|
||||||
<UButton v-if="isEditing" @click="saveText" color="neutral" variant="outline" class="p-2.5 cursor-pointer">
|
|
||||||
<p class="dark:text-white text-black">Save the edited text</p>
|
|
||||||
</UButton>
|
|
||||||
<UButton v-if="isEditing" @click="cancelEdit" color="neutral" variant="outline"
|
|
||||||
class="p-2.5 cursor-pointer">
|
|
||||||
Cancel
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
<p ref="usageRef" v-html="productStore.productDescription.usage"
|
|
||||||
class="flex flex-col justify-center w-full text-start dark:text-white! text-black!"></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="activeTab === 'description'"
|
<UEditor v-if="isTranslations" v-slot="{ editor }" v-model="productStore.productDescription.usage"
|
||||||
class="px-8 py-4 border border-(--border-light) dark:border-(--border-dark) rounded-md bg-(--second-light) dark:bg-(--main-dark)">
|
content-type="html" :ui="{ base: 'p-8 sm:px-16' }" class="w-full min-h-74" placeholder="Write there ...">
|
||||||
<div class="flex items-center justify-end gap-3 mb-4">
|
<UEditorToolbar :editor="editor" :items="toolbarItems" class="sm:px-8">
|
||||||
<UButton v-if="!descriptionEdit.isEditing.value" @click="enableDescriptionEdit"
|
<template #link>
|
||||||
class="flex items-center gap-2 m-2 cursor-pointer bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)!">
|
<EditorLinkPopover :editor="editor" auto-open />
|
||||||
<p class="text-white">Change Text</p>
|
</template>
|
||||||
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-[30px] text-white!" />
|
</UEditorToolbar>
|
||||||
</UButton>
|
</UEditor>
|
||||||
<UButton v-if="descriptionEdit.isEditing.value" @click="saveDescription" color="neutral" variant="outline"
|
<p v-else v-html="productStore.productDescription.usage" class="text-black dark:text-white"></p>
|
||||||
class="p-2.5 cursor-pointer">
|
</div>
|
||||||
<p class="dark:text-white text-black ">Save the edited text</p>
|
</template>
|
||||||
</UButton>
|
</UTabs>
|
||||||
<UButton v-if="descriptionEdit.isEditing.value" @click="cancelDescriptionEdit" color="neutral"
|
|
||||||
variant="outline" class="p-2.5 cursor-pointer">Cancel</UButton>
|
|
||||||
</div>
|
|
||||||
<div ref="descriptionRef" v-html="productStore.productDescription.description"
|
|
||||||
class="flex flex-col justify-center dark:text-white text-black">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</component>
|
</component>
|
||||||
@@ -162,11 +197,141 @@ import Default from '@/layouts/default.vue';
|
|||||||
import { langs } from '@/router/langs';
|
import { langs } from '@/router/langs';
|
||||||
import { useProductStore } from '@/stores/product';
|
import { useProductStore } from '@/stores/product';
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
import { watch } from 'vue';
|
import type { EditorToolbarItem } from '@nuxt/ui';
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref, watch } from 'vue';
|
||||||
import { computed } from 'vue';
|
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
const toolbarItems: EditorToolbarItem[][] = [
|
||||||
|
// History controls
|
||||||
|
[{
|
||||||
|
kind: 'undo',
|
||||||
|
icon: 'i-lucide-undo',
|
||||||
|
tooltip: { text: 'Undo' }
|
||||||
|
}, {
|
||||||
|
kind: 'redo',
|
||||||
|
icon: 'i-lucide-redo',
|
||||||
|
tooltip: { text: 'Redo' }
|
||||||
|
}],
|
||||||
|
// Block types
|
||||||
|
[{
|
||||||
|
icon: 'i-lucide-heading',
|
||||||
|
tooltip: { text: 'Headings' },
|
||||||
|
content: {
|
||||||
|
align: 'start'
|
||||||
|
},
|
||||||
|
items: [{
|
||||||
|
kind: 'heading',
|
||||||
|
level: 1,
|
||||||
|
icon: 'i-lucide-heading-1',
|
||||||
|
label: 'Heading 1'
|
||||||
|
}, {
|
||||||
|
kind: 'heading',
|
||||||
|
level: 2,
|
||||||
|
icon: 'i-lucide-heading-2',
|
||||||
|
label: 'Heading 2'
|
||||||
|
}, {
|
||||||
|
kind: 'heading',
|
||||||
|
level: 3,
|
||||||
|
icon: 'i-lucide-heading-3',
|
||||||
|
label: 'Heading 3'
|
||||||
|
}, {
|
||||||
|
kind: 'heading',
|
||||||
|
level: 4,
|
||||||
|
icon: 'i-lucide-heading-4',
|
||||||
|
label: 'Heading 4'
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
icon: 'i-lucide-list',
|
||||||
|
tooltip: { text: 'Lists' },
|
||||||
|
content: {
|
||||||
|
align: 'start'
|
||||||
|
},
|
||||||
|
items: [{
|
||||||
|
kind: 'bulletList',
|
||||||
|
icon: 'i-lucide-list',
|
||||||
|
label: 'Bullet List'
|
||||||
|
}, {
|
||||||
|
kind: 'orderedList',
|
||||||
|
icon: 'i-lucide-list-ordered',
|
||||||
|
label: 'Ordered List'
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
kind: 'blockquote',
|
||||||
|
icon: 'i-lucide-text-quote',
|
||||||
|
tooltip: { text: 'Blockquote' }
|
||||||
|
}, {
|
||||||
|
kind: 'codeBlock',
|
||||||
|
icon: 'i-lucide-square-code',
|
||||||
|
tooltip: { text: 'Code Block' }
|
||||||
|
}, {
|
||||||
|
kind: 'horizontalRule',
|
||||||
|
icon: 'i-lucide-separator-horizontal',
|
||||||
|
tooltip: { text: 'Horizontal Rule' }
|
||||||
|
}],
|
||||||
|
// Text formatting
|
||||||
|
[{
|
||||||
|
kind: 'mark',
|
||||||
|
mark: 'bold',
|
||||||
|
icon: 'i-lucide-bold',
|
||||||
|
tooltip: { text: 'Bold' }
|
||||||
|
}, {
|
||||||
|
kind: 'mark',
|
||||||
|
mark: 'italic',
|
||||||
|
icon: 'i-lucide-italic',
|
||||||
|
tooltip: { text: 'Italic' }
|
||||||
|
}, {
|
||||||
|
kind: 'mark',
|
||||||
|
mark: 'underline',
|
||||||
|
icon: 'i-lucide-underline',
|
||||||
|
tooltip: { text: 'Underline' }
|
||||||
|
}, {
|
||||||
|
kind: 'mark',
|
||||||
|
mark: 'strike',
|
||||||
|
icon: 'i-lucide-strikethrough',
|
||||||
|
tooltip: { text: 'Strikethrough' }
|
||||||
|
}, {
|
||||||
|
kind: 'mark',
|
||||||
|
mark: 'code',
|
||||||
|
icon: 'i-lucide-code',
|
||||||
|
tooltip: { text: 'Code' }
|
||||||
|
}],
|
||||||
|
// Link
|
||||||
|
[{
|
||||||
|
kind: 'link',
|
||||||
|
icon: 'i-lucide-link',
|
||||||
|
tooltip: { text: 'Link' }
|
||||||
|
}],
|
||||||
|
// Text alignment
|
||||||
|
[{
|
||||||
|
icon: 'i-lucide-align-justify',
|
||||||
|
tooltip: { text: 'Text Align' },
|
||||||
|
content: {
|
||||||
|
align: 'end'
|
||||||
|
},
|
||||||
|
items: [{
|
||||||
|
kind: 'textAlign',
|
||||||
|
align: 'left',
|
||||||
|
icon: 'i-lucide-align-left',
|
||||||
|
label: 'Align Left'
|
||||||
|
}, {
|
||||||
|
kind: 'textAlign',
|
||||||
|
align: 'center',
|
||||||
|
icon: 'i-lucide-align-center',
|
||||||
|
label: 'Align Center'
|
||||||
|
}, {
|
||||||
|
kind: 'textAlign',
|
||||||
|
align: 'right',
|
||||||
|
icon: 'i-lucide-align-right',
|
||||||
|
label: 'Align Right'
|
||||||
|
}, {
|
||||||
|
kind: 'textAlign',
|
||||||
|
align: 'justify',
|
||||||
|
icon: 'i-lucide-align-justify',
|
||||||
|
label: 'Align Justify'
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
function backFromProduct() {
|
function backFromProduct() {
|
||||||
let path = localStorage.getItem('back_from_product')
|
let path = localStorage.getItem('back_from_product')
|
||||||
@@ -192,9 +357,7 @@ const settingStore = useSettingsStore()
|
|||||||
const productStore = useProductStore()
|
const productStore = useProductStore()
|
||||||
const isTranslations = ref(false)
|
const isTranslations = ref(false)
|
||||||
|
|
||||||
const toLangId = ref(null)
|
const toLangId = ref<number>(settingStore.shopDefaultLanguage)
|
||||||
|
|
||||||
const availableLangs = computed(() => langs.filter(item => item.id !== settingStore.shopDefaultLanguage))
|
|
||||||
const productID = ref<number>(0)
|
const productID = ref<number>(0)
|
||||||
const translating = ref(false)
|
const translating = ref(false)
|
||||||
|
|
||||||
@@ -207,8 +370,6 @@ const descriptionRef = ref<HTMLElement | null>(null)
|
|||||||
const descriptionEdit = useEditable(descriptionRef)
|
const descriptionEdit = useEditable(descriptionRef)
|
||||||
const originalDescription = ref('')
|
const originalDescription = ref('')
|
||||||
|
|
||||||
const isShowProductView = ref(false)
|
|
||||||
|
|
||||||
const translateToSelectedLanguage = async () => {
|
const translateToSelectedLanguage = async () => {
|
||||||
if (toLangId.value && productID.value) {
|
if (toLangId.value && productID.value) {
|
||||||
translating.value = true
|
translating.value = true
|
||||||
@@ -216,6 +377,8 @@ const translateToSelectedLanguage = async () => {
|
|||||||
await productStore.translateProductDescription(productID.value, toLangId.value)
|
await productStore.translateProductDescription(productID.value, toLangId.value)
|
||||||
} finally {
|
} finally {
|
||||||
translating.value = false
|
translating.value = false
|
||||||
|
enableEdit()
|
||||||
|
enableDescriptionEdit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (productStore.productDescription) {
|
if (productStore.productDescription) {
|
||||||
@@ -226,9 +389,14 @@ const translateToSelectedLanguage = async () => {
|
|||||||
const fetchForLanguage = async (langId: number | null) => {
|
const fetchForLanguage = async (langId: number | null) => {
|
||||||
if (productID.value) {
|
if (productID.value) {
|
||||||
await productStore.getProductDescription(langId, productID.value)
|
await productStore.getProductDescription(langId, productID.value)
|
||||||
|
removeInlineStylesFromAll(productStore.productDescription)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(toLangId, () => {
|
||||||
|
fetchForLanguage(toLangId.value)
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const id = route.params.product_id
|
const id = route.params.product_id
|
||||||
if (id) {
|
if (id) {
|
||||||
@@ -237,8 +405,15 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const items = ref([
|
||||||
|
{ label: 'Description', slot: 'description', name: 'description' },
|
||||||
|
{ label: 'Usage', slot: 'usage', name: 'usage' }
|
||||||
|
])
|
||||||
|
|
||||||
// text edit
|
// text edit
|
||||||
const enableEdit = () => {
|
const enableEdit = () => {
|
||||||
|
console.log(usageRef.value, 'usageRef');
|
||||||
|
|
||||||
if (usageRef.value) {
|
if (usageRef.value) {
|
||||||
originalUsage.value = usageRef.value.innerHTML
|
originalUsage.value = usageRef.value.innerHTML
|
||||||
}
|
}
|
||||||
@@ -246,6 +421,15 @@ const enableEdit = () => {
|
|||||||
usageEdit.enableEdit()
|
usageEdit.enableEdit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const enableDescriptionEdit = () => {
|
||||||
|
console.log(usageRef.value, 'usageRef');
|
||||||
|
|
||||||
|
if (descriptionRef.value) {
|
||||||
|
originalDescription.value = descriptionRef.value.innerHTML
|
||||||
|
}
|
||||||
|
descriptionEdit.enableEdit()
|
||||||
|
}
|
||||||
|
|
||||||
const saveText = () => {
|
const saveText = () => {
|
||||||
if (usageRef.value) {
|
if (usageRef.value) {
|
||||||
productStore.productDescription.usage = usageRef.value.innerHTML
|
productStore.productDescription.usage = usageRef.value.innerHTML
|
||||||
@@ -265,13 +449,6 @@ const cancelEdit = () => {
|
|||||||
isEditing.value = false
|
isEditing.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const enableDescriptionEdit = () => {
|
|
||||||
if (descriptionRef.value) {
|
|
||||||
originalDescription.value = descriptionRef.value.innerHTML
|
|
||||||
}
|
|
||||||
descriptionEdit.enableEdit()
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveDescription = async () => {
|
const saveDescription = async () => {
|
||||||
if (descriptionRef.value) {
|
if (descriptionRef.value) {
|
||||||
productStore.productDescription.description = descriptionRef.value.innerHTML
|
productStore.productDescription.description = descriptionRef.value.innerHTML
|
||||||
@@ -290,4 +467,58 @@ const cancelDescriptionEdit = () => {
|
|||||||
}
|
}
|
||||||
descriptionEdit.disableEdit()
|
descriptionEdit.disableEdit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeInlineStylesFromAll(product) {
|
||||||
|
if (!product) return
|
||||||
|
const removeStyles = (html: string) => {
|
||||||
|
if (!html) return ''
|
||||||
|
const div = document.createElement('div')
|
||||||
|
div.innerHTML = html
|
||||||
|
|
||||||
|
div.querySelectorAll('*').forEach(el => {
|
||||||
|
const bg = el.style.background || el.style.backgroundColor
|
||||||
|
if (bg) {
|
||||||
|
el.style.background = ''
|
||||||
|
el.style.backgroundColor = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
div.querySelectorAll('p').forEach(p => {
|
||||||
|
if (p.querySelector('img')) {
|
||||||
|
p.style.display = 'flex'
|
||||||
|
p.style.gap = '20px'
|
||||||
|
p.style.padding = '30px 0'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
div.querySelectorAll('table').forEach(table => {
|
||||||
|
table.querySelectorAll('tbody').forEach(tbody => {
|
||||||
|
tbody.style.setProperty('background-color', '#F8FAFC', 'important')
|
||||||
|
tbody.querySelectorAll('tr').forEach(tr => {
|
||||||
|
tr.querySelectorAll('td').forEach(td => {
|
||||||
|
td.style.setProperty('padding', '10px', 'important')
|
||||||
|
td.style.setProperty('border-top', '1pt solid black', 'important')
|
||||||
|
td.style.setProperty('border-left', '1pt solid black', 'important')
|
||||||
|
td.style.setProperty('border-bottom', '1pt solid black', 'important')
|
||||||
|
td.style.setProperty('border-right', '1pt solid black', 'important')
|
||||||
|
|
||||||
|
td.querySelectorAll('p').forEach(p => {
|
||||||
|
p.style.setProperty('display', 'flex', 'important')
|
||||||
|
p.style.setProperty('padding', '0px', 'important')
|
||||||
|
p.style.setProperty('align-items', 'start', 'important')
|
||||||
|
p.style.setProperty('color', 'black', 'important')
|
||||||
|
p.style.setProperty('font-size', '14px', 'important')
|
||||||
|
p.querySelectorAll('span').forEach(span => {
|
||||||
|
span.style.setProperty('color', 'black', 'important')
|
||||||
|
span.style.setProperty('font-size', '14px', 'important')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return div.innerHTML
|
||||||
|
}
|
||||||
|
product.description = removeStyles(product.description)
|
||||||
|
product.description_short = removeStyles(product.description_short)
|
||||||
|
product.usage = removeStyles(product.usage)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
<UInput v-model="searchQuery" type="text" :placeholder="t('Search address')"
|
<UInput v-model="searchQuery" type="text" :placeholder="t('Search address')"
|
||||||
class="bg-white dark:bg-gray-800 text-black dark:text-white absolute" />
|
class="bg-white dark:bg-gray-800 text-black dark:text-white absolute" />
|
||||||
<UIcon name="ic:baseline-search"
|
<UIcon name="ic:baseline-search"
|
||||||
class="text-[20px] text-(--accent-blue-light) dark:text-(--accent-blue-dark) relative left-40" />
|
class="text-[20px] text-(--text-sky-light) dark:text-(--text-sky-dark) relative left-40" />
|
||||||
</div>
|
</div>
|
||||||
<UButton color="primary" @click="openCreateModal"
|
<UButton color="primary" @click="openCreateModal"
|
||||||
class="bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) text-white hover:bg-(--accent-blue-dark) dark:hover:bg-(--accent-blue-light)">
|
class="bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) text-white hover:bg-(--accent-blue-dark) dark:hover:bg-(--accent-blue-light)">
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<UIcon name="material-symbols:delete" class="text-[18px]" />
|
<UIcon name="material-symbols:delete" class="text-[18px]" />
|
||||||
</button>
|
</button>
|
||||||
<UButton size="sm" color="neutral" variant="outline" @click="openEditModal(address)"
|
<UButton size="sm" color="neutral" variant="outline" @click="openEditModal(address)"
|
||||||
class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) text-[13px]">
|
class="text-(--text-sky-light) dark:text-(--text-sky-dark) text-[13px]">
|
||||||
{{ t('edit') }}
|
{{ t('edit') }}
|
||||||
<UIcon name="ic:sharp-edit" class="text-[15px]" />
|
<UIcon name="ic:sharp-edit" class="text-[15px]" />
|
||||||
</UButton>
|
</UButton>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
name: 'customer-product', params: {
|
name: 'customer-product', params: {
|
||||||
product_id: '51'
|
product_id: '51'
|
||||||
}
|
}
|
||||||
}" class="inline-block mt-4 text-(--accent-blue-light) dark:text-(--accent-blue-dark) hover:underline">
|
}" class="inline-block mt-4 text-(--text-sky-light) dark:text-(--text-sky-dark) hover:underline">
|
||||||
{{ t('Continue Shopping') }}
|
{{ t('Continue Shopping') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between mb-6">
|
<div class="flex justify-between mb-6">
|
||||||
<span class="text-black dark:text-white font-semibold text-lg">{{ t('Total') }}</span>
|
<span class="text-black dark:text-white font-semibold text-lg">{{ t('Total') }}</span>
|
||||||
<span class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) font-bold text-lg">${{
|
<span class="text-(--text-sky-light) dark:text-(--text-sky-dark) font-bold text-lg">${{
|
||||||
cartStore.orderTotal.toFixed(2) }}</span>
|
cartStore.orderTotal.toFixed(2) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
|
? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
|
||||||
: 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'">
|
: 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'">
|
||||||
<input type="radio" :value="address.id" v-model="selectedAddress"
|
<input type="radio" :value="address.id" v-model="selectedAddress"
|
||||||
class="mt-1 w-4 h-4 text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
class="mt-1 w-4 h-4 text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="text-black dark:text-white font-medium">{{ address.street }}</p>
|
<p class="text-black dark:text-white font-medium">{{ address.street }}</p>
|
||||||
<p class="text-gray-600 dark:text-gray-400 text-sm">{{ address.zipCode }}, {{ address.city }}</p>
|
<p class="text-gray-600 dark:text-gray-400 text-sm">{{ address.zipCode }}, {{ address.city }}</p>
|
||||||
@@ -115,7 +115,7 @@
|
|||||||
<UIcon name="mdi:map-marker-outline" class="text-4xl text-gray-400 mb-2" />
|
<UIcon name="mdi:map-marker-outline" class="text-4xl text-gray-400 mb-2" />
|
||||||
<p class="text-gray-500 dark:text-gray-400">{{ t('No addresses found') }}</p>
|
<p class="text-gray-500 dark:text-gray-400">{{ t('No addresses found') }}</p>
|
||||||
<RouterLink :to="{ name: 'addresses' }"
|
<RouterLink :to="{ name: 'addresses' }"
|
||||||
class="inline-block mt-2 text-(--accent-blue-light) dark:text-(--accent-blue-dark) hover:underline">
|
class="inline-block mt-2 text-(--text-sky-light) dark:text-(--text-sky-dark) hover:underline">
|
||||||
{{ t('Add Address') }}
|
{{ t('Add Address') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
@@ -131,11 +131,11 @@
|
|||||||
? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
|
? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
|
||||||
: 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'">
|
: 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'">
|
||||||
<input type="radio" :value="method.id" v-model="selectedDeliveryMethod"
|
<input type="radio" :value="method.id" v-model="selectedDeliveryMethod"
|
||||||
class="w-4 h-4 text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
class="w-4 h-4 text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<span class="text-black dark:text-white font-medium">{{ method.name }}</span>
|
<span class="text-black dark:text-white font-medium">{{ method.name }}</span>
|
||||||
<span class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) font-medium">
|
<span class="text-(--text-sky-light) dark:text-(--text-sky-dark) font-medium">
|
||||||
{{ method.price > 0 ? `$${method.price.toFixed(2)}` : t('Free') }}
|
{{ method.price > 0 ? `$${method.price.toFixed(2)}` : t('Free') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<p class="text-gray-600 dark:text-gray-300">
|
<p class="text-gray-600 dark:text-gray-300">
|
||||||
{{ productData.description }}
|
{{ productData.description }}
|
||||||
</p>
|
</p>
|
||||||
<div class="text-3xl font-bold text-(--accent-blue-light) dark:text-(--accent-blue-dark)">
|
<div class="text-3xl font-bold text-(--text-sky-light) dark:text-(--text-sky-dark)">
|
||||||
{{ productData.price }}
|
{{ productData.price }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex md:flex-row flex-col justify-between md:items-end items-start gap-5 md:gap-0 md:mb-8 mb-4">
|
<div class="flex md:flex-row flex-col justify-between md:items-end items-start gap-5 md:gap-0 md:mb-8 mb-4">
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<span class="text-sm text-(--accent-blue-light) dark:text-(--accent-blue-dark) ">Colors:</span>
|
<span class="text-sm text-(--text-sky-light) dark:text-(--text-sky-dark) ">Colors:</span>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button v-for="color in productData.colors" :key="color.id" @click="selectedColor = color"
|
<button v-for="color in productData.colors" :key="color.id" @click="selectedColor = color"
|
||||||
class="w-10 h-10 border-2 transition-all" :class="selectedColor?.id === color.id
|
class="w-10 h-10 border-2 transition-all" :class="selectedColor?.id === color.id
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) p-4">
|
class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) p-4">
|
||||||
<h2 class="text-xl font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
<h2 class="text-xl font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
||||||
<UIcon name="mdi:domain"
|
<UIcon name="mdi:domain"
|
||||||
class="text-[24px] text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
class="text-[24px] text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
||||||
{{ t('Company Information') }}
|
{{ t('Company Information') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid grid-cols-1 gap-10">
|
<div class="grid grid-cols-1 gap-10">
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
<h2
|
<h2
|
||||||
class="text-xl font-semibold text-black dark:text-white mb-2 flex items-center gap-2">
|
class="text-xl font-semibold text-black dark:text-white mb-2 flex items-center gap-2">
|
||||||
<UIcon name="mdi:map-marker"
|
<UIcon name="mdi:map-marker"
|
||||||
class="text-[24px] text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
class="text-[24px] text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
||||||
{{ t('Addresses') }}
|
{{ t('Addresses') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<UButton color="primary" variant="outline"
|
<UButton color="primary" variant="outline"
|
||||||
class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) border-(--accent-blue-light) dark:border-(--accent-blue-dark)"
|
class="text-(--text-sky-light) dark:text-(--text-sky-dark) border-(--accent-blue-light) dark:border-(--accent-blue-dark)"
|
||||||
@click="goToCreateAccount">
|
@click="goToCreateAccount">
|
||||||
<UIcon name="ic:sharp-edit" />
|
<UIcon name="ic:sharp-edit" />
|
||||||
{{ t('Edit Account') }}
|
{{ t('Edit Account') }}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<h2
|
<h2
|
||||||
class="text-lg font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
class="text-lg font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
||||||
<UIcon name="mdi:domain"
|
<UIcon name="mdi:domain"
|
||||||
class="text-[20px] text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
class="text-[20px] text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
||||||
{{ t('Company Information') }}
|
{{ t('Company Information') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
<h2
|
<h2
|
||||||
class="text-lg font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
class="text-lg font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
||||||
<UIcon name="mdi:map-marker"
|
<UIcon name="mdi:map-marker"
|
||||||
class="text-[20px] text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
class="text-[20px] text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
||||||
{{ t('Select Addresses') }}
|
{{ t('Select Addresses') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="bg-(--second-light) dark:bg-(--main-dark)">
|
<div class="bg-(--second-light) dark:bg-(--main-dark)">
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
|
? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
|
||||||
: 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'">
|
: 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'">
|
||||||
<input type="radio" :value="address.id" v-model="selectedAddress"
|
<input type="radio" :value="address.id" v-model="selectedAddress"
|
||||||
class="mt-1 w-4 h-4 text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
class="mt-1 w-4 h-4 text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="text-black dark:text-white font-medium">{{ address.street }}
|
<p class="text-black dark:text-white font-medium">{{ address.street }}
|
||||||
</p>
|
</p>
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
<UIcon name="mdi:map-marker-outline" class="text-4xl text-gray-400 mb-2" />
|
<UIcon name="mdi:map-marker-outline" class="text-4xl text-gray-400 mb-2" />
|
||||||
<p class="text-gray-500 dark:text-gray-400">{{ t('No addresses found') }}</p>
|
<p class="text-gray-500 dark:text-gray-400">{{ t('No addresses found') }}</p>
|
||||||
<RouterLink :to="{ name: 'addresses' }"
|
<RouterLink :to="{ name: 'addresses' }"
|
||||||
class="inline-block mt-2 text-(--accent-blue-light) dark:text-(--accent-blue-dark) hover:underline">
|
class="inline-block mt-2 text-(--text-sky-light) dark:text-(--text-sky-dark) hover:underline">
|
||||||
{{ t('Add Address') }}
|
{{ t('Add Address') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-1">
|
<div class="flex flex-1 overflow-x-hidden h-svh">
|
||||||
<USidebar v-model:open="open" collapsible="icon" rail :ui="{
|
<USidebar v-model:open="open" collapsible="icon" rail :ui="{
|
||||||
container: 'h-full',
|
container: 'h-full z-80',
|
||||||
inner: 'bg-elevated/25 divide-transparent',
|
inner: 'bg-elevated/25 divide-transparent',
|
||||||
body: 'py-0'
|
body: 'py-0'
|
||||||
}">
|
}">
|
||||||
@@ -32,10 +32,10 @@
|
|||||||
</USidebar>
|
</USidebar>
|
||||||
|
|
||||||
<div class="flex-1 flex flex-col">
|
<div class="flex-1 flex flex-col">
|
||||||
<div class="h-(--ui-header-height) shrink-0 flex items-center justify-between px-4 border-b border-default">
|
<div class="flex h-(--ui-header-height) shrink-0 items-center justify-between px-4 border-b border-default">
|
||||||
<UButton icon="i-lucide-panel-left" color="neutral" variant="ghost" aria-label="Toggle sidebar"
|
<UButton icon="i-lucide-panel-left" color="neutral" variant="ghost" aria-label="Toggle sidebar"
|
||||||
@click="open = !open" />
|
@click="open = !open" />
|
||||||
<div class="flex items-center gap-12">
|
<div class="hidden md:flex items-center gap-12">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<CountryCurrencySwitch />
|
<CountryCurrencySwitch />
|
||||||
<LangSwitch />
|
<LangSwitch />
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-1 p-4">
|
<div class="flex-1 p-4 bg-slate-50 h-svh">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -158,6 +158,7 @@ import { useFetchJson } from '@/composable/useFetchJson'
|
|||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import CountryCurrencySwitch from '@/components/inner/CountryCurrencySwitch.vue'
|
import CountryCurrencySwitch from '@/components/inner/CountryCurrencySwitch.vue'
|
||||||
import LangSwitch from '@/components/inner/LangSwitch.vue'
|
import LangSwitch from '@/components/inner/LangSwitch.vue'
|
||||||
|
import ThemeSwitch from '@/components/inner/ThemeSwitch.vue'
|
||||||
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ export const useProductStore = defineStore('product', () => {
|
|||||||
try {
|
try {
|
||||||
const response = await useFetchJson<ProductDescription>(`/api/v1/restricted/product-translation/translate-product-description?productID=${productID}&productFromLangID=${settingStore.shopDefaultLanguage}&productToLangID=${toLangId}&model=${model}`)
|
const response = await useFetchJson<ProductDescription>(`/api/v1/restricted/product-translation/translate-product-description?productID=${productID}&productFromLangID=${settingStore.shopDefaultLanguage}&productToLangID=${toLangId}&model=${model}`)
|
||||||
productDescription.value = response.items
|
productDescription.value = response.items
|
||||||
saveProductDescription(productID, toLangId)
|
|
||||||
return response.items
|
return response.items
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
error.value = e?.message || 'Failed to translate product description'
|
error.value = e?.message || 'Failed to translate product description'
|
||||||
@@ -64,42 +63,6 @@ export const useProductStore = defineStore('product', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fixHtml(html: string) {
|
|
||||||
return html
|
|
||||||
// 1. fix img
|
|
||||||
.replace(/<img([^>]*?)>/g, '<img$1 />')
|
|
||||||
|
|
||||||
// 2. escape text only
|
|
||||||
.replace(/>([^<]+)</g, (match, text) => {
|
|
||||||
const escaped = text
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''')
|
|
||||||
|
|
||||||
return `>${escaped}<`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function fixAll(obj: any): any {
|
|
||||||
if (typeof obj === 'string') {
|
|
||||||
return fixHtml(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(obj)) {
|
|
||||||
return obj.map(fixAll)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof obj === 'object' && obj !== null) {
|
|
||||||
const result: any = {}
|
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
|
||||||
result[key] = fixAll(value)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveProductDescription(productID?: number, langId?: number | null) {
|
async function saveProductDescription(productID?: number, langId?: number | null) {
|
||||||
const id = productID || 1
|
const id = productID || 1
|
||||||
const lang = langId || 1
|
const lang = langId || 1
|
||||||
@@ -121,7 +84,6 @@ export const useProductStore = defineStore('product', () => {
|
|||||||
available_now: productDescription.value?.available_now || '',
|
available_now: productDescription.value?.available_now || '',
|
||||||
available_later: productDescription.value?.available_later || '',
|
available_later: productDescription.value?.available_later || '',
|
||||||
usage: productDescription.value?.usage || '',
|
usage: productDescription.value?.usage || '',
|
||||||
// delivery_in_stock: stripHtml(productDescription.value?.delivery_in_stock || '')
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ const PrivacyComponent = computed(() =>
|
|||||||
|
|
||||||
<div class="flex items-center justify-between w-full dark:text-white text-black">
|
<div class="flex items-center justify-between w-full dark:text-white text-black">
|
||||||
<button variant="link" size="sm" @click="goToPasswordRecovery"
|
<button variant="link" size="sm" @click="goToPasswordRecovery"
|
||||||
class="text-[15px] w-full flex justify-end text-(--accent-blue-light) dark:text-(--accent-blue-dark) cursor-pointer">
|
class="text-[15px] w-full flex justify-end text-(--text-sky-light) dark:text-(--text-sky-dark) cursor-pointer">
|
||||||
{{ $t('general.forgot_password') }}?
|
{{ $t('general.forgot_password') }}?
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -145,18 +145,18 @@ const PrivacyComponent = computed(() =>
|
|||||||
<p class="dark:text-white text-black">
|
<p class="dark:text-white text-black">
|
||||||
{{ $t('general.dont_have_an_account') }}?
|
{{ $t('general.dont_have_an_account') }}?
|
||||||
<button variant="link" size="sm"
|
<button variant="link" size="sm"
|
||||||
class="text-[15px] text-(--accent-blue-light) dark:text-(--accent-blue-dark) cursor-pointer"
|
class="text-[15px] text-(--text-sky-light) dark:text-(--text-sky-dark) cursor-pointer"
|
||||||
@click="goToRegister">{{ $t('general.create_account_now') }}</button>
|
@click="goToRegister">{{ $t('general.create_account_now') }}</button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-8 text-center text-xs dark:text-white text-black">
|
<p class="mt-8 text-center text-xs dark:text-white text-black">
|
||||||
{{ $t('general.by_signing_in_you_agree_to_our') }}
|
{{ $t('general.by_signing_in_you_agree_to_our') }}
|
||||||
<span @click="showTherms = !showTherms"
|
<span @click="showTherms = !showTherms"
|
||||||
class="cursor-pointer underline text-(--accent-blue-light) dark:text-(--accent-blue-dark) cursor-pointer">{{
|
class="cursor-pointer underline text-(--text-sky-light) dark:text-(--text-sky-dark) cursor-pointer">{{
|
||||||
$t('general.terms_of_service') }}</span>
|
$t('general.terms_of_service') }}</span>
|
||||||
{{ $t('general.and') }}
|
{{ $t('general.and') }}
|
||||||
<span @click="showPrivacy = !showPrivacy"
|
<span @click="showPrivacy = !showPrivacy"
|
||||||
class="cursor-pointer underline text-(--accent-blue-light) dark:text-(--accent-blue-dark) cursor-pointer">{{
|
class="cursor-pointer underline text-(--text-sky-light) dark:text-(--text-sky-dark) cursor-pointer">{{
|
||||||
$t('general.privacy_policy') }}</span>
|
$t('general.privacy_policy') }}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -92,13 +92,13 @@ function validate(): FormError[] {
|
|||||||
class="w-full flex items-center gap-2 justify-center text-[15px] dark:text-white text-black cursor-pointer"
|
class="w-full flex items-center gap-2 justify-center text-[15px] dark:text-white text-black cursor-pointer"
|
||||||
@click="goToLogin">
|
@click="goToLogin">
|
||||||
<UIcon name="mingcute:arrow-left-line"
|
<UIcon name="mingcute:arrow-left-line"
|
||||||
class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) text-[16px]" />
|
class="text-(--text-sky-light) dark:text-(--text-sky-dark) text-[16px]" />
|
||||||
{{ $t('general.back_to_sign_in') }}
|
{{ $t('general.back_to_sign_in') }}
|
||||||
</button>
|
</button>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
{{ $t('general.dont_have_an_account') }}
|
{{ $t('general.dont_have_an_account') }}
|
||||||
<button variant="link" size="sm" @click="goToRegister"
|
<button variant="link" size="sm" @click="goToRegister"
|
||||||
class=" text-[15px] text-(--accent-blue-light) dark:text-(--accent-blue-dark) cursor-pointer">{{
|
class=" text-[15px] text-(--text-sky-light) dark:text-(--text-sky-dark) cursor-pointer">{{
|
||||||
$t('general.create_account_now') }}
|
$t('general.create_account_now') }}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -83,12 +83,12 @@
|
|||||||
<span class="dark:text-white text-black">
|
<span class="dark:text-white text-black">
|
||||||
{{ $t('general.i_agree_to_the') }}
|
{{ $t('general.i_agree_to_the') }}
|
||||||
<span @click="showTherms = !showTherms"
|
<span @click="showTherms = !showTherms"
|
||||||
class="cursor-pointer underline text-(--accent-blue-light) dark:text-(--accent-blue-dark)">{{
|
class="cursor-pointer underline text-(--text-sky-light) dark:text-(--text-sky-dark)">{{
|
||||||
$t('general.terms_of_service')
|
$t('general.terms_of_service')
|
||||||
}}</span>
|
}}</span>
|
||||||
{{ $t('general.and') }}
|
{{ $t('general.and') }}
|
||||||
<span @click="showPrivacy = !showPrivacy"
|
<span @click="showPrivacy = !showPrivacy"
|
||||||
class="cursor-pointer underline text-(--accent-blue-light) dark:text-(--accent-blue-dark)">{{
|
class="cursor-pointer underline text-(--text-sky-light) dark:text-(--text-sky-dark)">{{
|
||||||
$t('general.privacy_policy')
|
$t('general.privacy_policy')
|
||||||
}}</span>
|
}}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ function validate(): FormError[] {
|
|||||||
|
|
||||||
<div class="text-center border-t dark:border-(--border-dark) border-(--border-light) pt-4">
|
<div class="text-center border-t dark:border-(--border-dark) border-(--border-light) pt-4">
|
||||||
<button color="neutral" variant="ghost" @click="goToLogin"
|
<button color="neutral" variant="ghost" @click="goToLogin"
|
||||||
class="text-[15px] flex items-center gap-2 text-(--accent-blue-light) dark:text-(--accent-blue-dark) cursor-pointer">
|
class="text-[15px] flex items-center gap-2 text-(--text-sky-light) dark:text-(--text-sky-dark) cursor-pointer">
|
||||||
<UIcon name="mingcute:arrow-left-line" />
|
<UIcon name="mingcute:arrow-left-line" />
|
||||||
{{ $t('general.back_to_sign_in') }}
|
{{ $t('general.back_to_sign_in') }}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ function goToLogin() {
|
|||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
{{ $t('verify_email.already_registered') }}
|
{{ $t('verify_email.already_registered') }}
|
||||||
<button variant="link" size="sm" @click="goToLogin"
|
<button variant="link" size="sm" @click="goToLogin"
|
||||||
class="cursor-pointer text-(--accent-blue-light) dark:text-(--accent-blue-dark)"> {{
|
class="cursor-pointer text-(--text-sky-light) dark:text-(--text-sky-dark)"> {{
|
||||||
$t('general.sign_in')
|
$t('general.sign_in')
|
||||||
}}
|
}}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user