fix some bugs
This commit is contained in:
@@ -0,0 +1 @@
|
||||
VITE_API_BASE_URL=https://parche.montech.ir/server/api
|
||||
@@ -1 +1,2 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="unocss" />
|
||||
|
||||
Generated
+1149
-9
File diff suppressed because it is too large
Load Diff
@@ -15,13 +15,18 @@
|
||||
"format": "prettier --write --experimental-cli src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@unocss/vite": "^66.7.5",
|
||||
"@vueuse/core": "^14.4.0",
|
||||
"jalaali-js": "^2.0.0",
|
||||
"lodash-es": "^4.18.1",
|
||||
"pinia": "^4.0.2",
|
||||
"unocss": "^66.7.5",
|
||||
"vue": "^3.5.40",
|
||||
"vue-router": "^5.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node24": "^24.0.4",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^24.13.3",
|
||||
"@vitejs/plugin-vue": "^6.0.8",
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.6",
|
||||
|
||||
+40
-7
@@ -1,26 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { RouterView } from 'vue-router'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { RouterView, useRouter } from 'vue-router'
|
||||
import { clamp } from 'lodash-es'
|
||||
import DefaultLayout from '@/layouts/DefaultLayout.vue'
|
||||
import ConfirmDialog from '@/components/ui/ConfirmDialog.vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useCartStore } from '@/stores/cart'
|
||||
import { useOrderStore } from '@/stores/order'
|
||||
import { useCatalogStore } from '@/stores/catalog'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const cartStore = useCartStore()
|
||||
const orderStore = useOrderStore()
|
||||
const catalogStore = useCatalogStore()
|
||||
const router = useRouter()
|
||||
|
||||
onMounted(() => {
|
||||
authStore.hydrate()
|
||||
cartStore.hydrate()
|
||||
orderStore.hydrate()
|
||||
const transitionName = ref('page-fade')
|
||||
|
||||
function routeDepth(path: string) {
|
||||
return path.split('/').filter(Boolean).length
|
||||
}
|
||||
|
||||
router.beforeEach((to, from) => {
|
||||
if (!from.name) {
|
||||
transitionName.value = 'page-fade'
|
||||
return
|
||||
}
|
||||
|
||||
const delta = clamp(routeDepth(to.path) - routeDepth(from.path), -1, 1)
|
||||
if (delta > 0) transitionName.value = 'page-forward'
|
||||
else if (delta < 0) transitionName.value = 'page-back'
|
||||
else transitionName.value = 'page-fade'
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await authStore.hydrate()
|
||||
if (authStore.isAuthenticated) {
|
||||
await Promise.all([
|
||||
catalogStore.loadCompanies(),
|
||||
cartStore.hydrate(),
|
||||
orderStore.hydrate(),
|
||||
])
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DefaultLayout>
|
||||
<RouterView />
|
||||
<RouterView v-slot="{ Component, route }">
|
||||
<Transition :name="transitionName" mode="out-in">
|
||||
<div :key="route.path" class="page-transition page-shell">
|
||||
<component :is="Component" />
|
||||
</div>
|
||||
</Transition>
|
||||
</RouterView>
|
||||
</DefaultLayout>
|
||||
<ConfirmDialog />
|
||||
</template>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
@@ -52,6 +53,16 @@
|
||||
max-width: var(--content-max-width);
|
||||
margin-inline: auto;
|
||||
padding-inline: var(--page-padding);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.page-transition {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
--color-tab-inactive-bg: #f1f5f9;
|
||||
--color-tab-inactive-text: #64748b;
|
||||
--color-summary-bg: #f1f5f9;
|
||||
--color-elevated: #ffffff;
|
||||
--color-brand-warm: #f3ece3;
|
||||
--color-brand-warm-text: #1a202c;
|
||||
--color-brand-warm-muted: #718096;
|
||||
--radius-sm: 8px;
|
||||
--radius-md: 12px;
|
||||
--radius-lg: 16px;
|
||||
@@ -52,6 +56,45 @@
|
||||
--desktop-breakpoint: 1200px;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
--color-primary: #7b9cff;
|
||||
--color-primary-hover: #9bb4ff;
|
||||
--color-primary-soft: #1b2740;
|
||||
--color-primary-dark: #9bb4ff;
|
||||
--color-primary-light: #1b2740;
|
||||
--color-success: #3dd68c;
|
||||
--color-success-light: #123528;
|
||||
--color-success-soft: #123528;
|
||||
--color-warning: #f5c14a;
|
||||
--color-warning-light: #3a2f14;
|
||||
--color-danger: #f47979;
|
||||
--color-danger-light: #3f1d22;
|
||||
--color-purple: #b39afa;
|
||||
--color-purple-light: #2a2144;
|
||||
--color-info: #7b9cff;
|
||||
--color-info-light: #1b2740;
|
||||
--color-bg: #0b0f14;
|
||||
--color-page-background: #0b0f14;
|
||||
--color-shell-bg: #080b10;
|
||||
--color-surface: #121821;
|
||||
--color-elevated: #1a2230;
|
||||
--color-border: #2a3444;
|
||||
--color-text: #eef2f8;
|
||||
--color-text-primary: #eef2f8;
|
||||
--color-text-muted: #9aa8bc;
|
||||
--color-text-secondary: #9aa8bc;
|
||||
--color-tab-inactive-bg: #1a2230;
|
||||
--color-tab-inactive-text: #9aa8bc;
|
||||
--color-summary-bg: #1a2230;
|
||||
--color-brand-warm: #2a241c;
|
||||
--color-brand-warm-text: #f3ebe0;
|
||||
--color-brand-warm-muted: #b9a88d;
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.45);
|
||||
--shadow-md: 0 8px 24px rgba(0, 0, 0, 0.45);
|
||||
--shadow-card: 0 1px 2px rgba(0, 0, 0, 0.35), 0 8px 24px rgba(0, 0, 0, 0.35);
|
||||
--shadow-frame: 0 12px 40px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
@@ -121,7 +164,8 @@ input[type='number'] {
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
padding: 12px var(--page-padding) 0;
|
||||
background: #ffffff;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.page__scroll {
|
||||
@@ -133,7 +177,7 @@ input[type='number'] {
|
||||
}
|
||||
|
||||
.page__scroll--with-nav {
|
||||
padding-bottom: 12px;
|
||||
padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@@ -152,11 +196,12 @@ input[type='number'] {
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--color-surface);
|
||||
background: var(--color-elevated);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--color-border);
|
||||
padding: 16px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.card + .card {
|
||||
@@ -296,11 +341,6 @@ input[type='number'] {
|
||||
}
|
||||
|
||||
/* Responsive visibility */
|
||||
.mobile-only,
|
||||
.mobile-block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.desktop-only {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -348,3 +388,51 @@ input[type='number'] {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Page route transitions */
|
||||
.page-fade-enter-active,
|
||||
.page-fade-leave-active,
|
||||
.page-forward-enter-active,
|
||||
.page-forward-leave-active,
|
||||
.page-back-enter-active,
|
||||
.page-back-leave-active {
|
||||
transition:
|
||||
opacity 0.22s ease,
|
||||
transform 0.22s ease;
|
||||
}
|
||||
|
||||
.page-fade-enter-from,
|
||||
.page-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.page-forward-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(-18px);
|
||||
}
|
||||
|
||||
.page-forward-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(12px);
|
||||
}
|
||||
|
||||
.page-back-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(18px);
|
||||
}
|
||||
|
||||
.page-back-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(-12px);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.page-fade-enter-active,
|
||||
.page-fade-leave-active,
|
||||
.page-forward-enter-active,
|
||||
.page-forward-leave-active,
|
||||
.page-back-enter-active,
|
||||
.page-back-leave-active {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-top: 48px;
|
||||
padding-bottom: 48px;
|
||||
justify-content: center;
|
||||
padding-block: 48px;
|
||||
}
|
||||
|
||||
.auth-header,
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, useAttrs } from 'vue'
|
||||
import icons from '@/components/BasicPacks/tools/icons/icons.json'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
icon: string
|
||||
color?: string | null
|
||||
size?: string | number
|
||||
strokeWidth?: string | number
|
||||
}>(),
|
||||
{
|
||||
color: null,
|
||||
size: 24,
|
||||
strokeWidth: 1.5,
|
||||
},
|
||||
)
|
||||
|
||||
defineOptions({ inheritAttrs: false })
|
||||
|
||||
const attrs = useAttrs()
|
||||
|
||||
const svgHtml = computed(() => {
|
||||
const raw = (icons as Record<string, string>)[props.icon]
|
||||
if (!raw) {
|
||||
if (import.meta.env.DEV) {
|
||||
console.warn(`[BasicIcon] missing icon: ${props.icon}`)
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
const size = String(props.size)
|
||||
return raw
|
||||
.replace(/\swidth="[^"]*"/, ` width="${size}"`)
|
||||
.replace(/\sheight="[^"]*"/, ` height="${size}"`)
|
||||
.replace(/stroke="#[^"]*"/g, 'stroke="currentColor"')
|
||||
})
|
||||
|
||||
const style = computed(() => ({
|
||||
color: props.color || undefined,
|
||||
width: `${props.size}px`,
|
||||
height: `${props.size}px`,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
lineHeight: 0,
|
||||
flexShrink: 0,
|
||||
}))
|
||||
const ariaHidden = computed(() => {
|
||||
const value = attrs['aria-hidden']
|
||||
if (value === undefined || value === null) return true
|
||||
return value as boolean | 'true' | 'false'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
class="basic-icon"
|
||||
v-bind="attrs"
|
||||
:style="style"
|
||||
:aria-hidden="ariaHidden"
|
||||
v-html="svgHtml"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.basic-icon :deep(svg) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.basic-icon {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.basic-icon :deep(svg path) {
|
||||
stroke: currentColor;
|
||||
stroke-width: v-bind('String(props.strokeWidth)');
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"vuesax-linear:add": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M6 12H18\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M12 18V6\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:arrow-down": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M19.9201 8.94995L13.4001 15.47C12.6301 16.24 11.3701 16.24 10.6001 15.47L4.08008 8.94995\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:arrow-left-1": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M15 19.9201L8.47997 13.4001C7.70997 12.6301 7.70997 11.3701 8.47997 10.6001L15 4.08008\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:arrow-right-1": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M8.91003 19.9201L15.43 13.4001C16.2 12.6301 16.2 11.3701 15.43 10.6001L8.91003 4.08008\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:bag-2": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M7.5 7.67001V6.70001C7.5 4.45001 9.31 2.24001 11.56 2.03001C14.24 1.77001 16.5 3.88001 16.5 6.51001V7.89001\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.99999 22H15C19.02 22 19.74 20.39 19.95 18.43L20.7 12.43C20.97 9.99 20.27 8 16 8H7.99999C3.72999 8 3.02999 9.99 3.29999 12.43L4.04999 18.43C4.25999 20.39 4.97999 22 8.99999 22Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M15.4955 12H15.5045\" stroke=\"#292D32\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.49451 12H8.50349\" stroke=\"#292D32\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:bag-cross": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M13.39 17.36L10.64 14.61\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M13.36 14.64L10.61 17.39\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.81 2L5.19 5.63\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M15.19 2L18.81 5.63\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M2 7.84998C2 5.99998 2.99 5.84998 4.22 5.84998H19.78C21.01 5.84998 22 5.99998 22 7.84998C22 9.99998 21.01 9.84998 19.78 9.84998H4.22C2.99 9.84998 2 9.99998 2 7.84998Z\" stroke=\"#292D32\" stroke-width=\"1.5\"/>\n<path d=\"M3.5 10L4.91 18.64C5.23 20.58 6 22 8.86 22H14.89C18 22 18.46 20.64 18.82 18.76L20.5 10\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:box-1": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M3.17004 7.43994L12 12.5499L20.77 7.46994\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M12 21.61V12.54\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M9.93001 2.48004L4.59001 5.44004C3.38001 6.11004 2.39001 7.79004 2.39001 9.17004V14.82C2.39001 16.2 3.38001 17.88 4.59001 18.55L9.93001 21.52C11.07 22.15 12.94 22.15 14.08 21.52L19.42 18.55C20.63 17.88 21.62 16.2 21.62 14.82V9.17004C21.62 7.79004 20.63 6.11004 19.42 5.44004L14.08 2.47004C12.93 1.84004 11.07 1.84004 9.93001 2.48004Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:box-remove": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M3.17004 7.43994L12 12.5499L20.77 7.46991\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M12 21.6099V12.5399\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M21.61 9.17V14.83C21.61 14.88 21.61 14.92 21.6 14.97C20.9 14.36 20 14 19 14C18.06 14 17.19 14.33 16.5 14.88C15.58 15.61 15 16.74 15 18C15 18.75 15.21 19.46 15.58 20.06C15.67 20.22 15.78 20.37 15.9 20.51L14.07 21.52C12.93 22.16 11.07 22.16 9.92999 21.52L4.59 18.56C3.38 17.89 2.39001 16.21 2.39001 14.83V9.17C2.39001 7.79 3.38 6.11002 4.59 5.44002L9.92999 2.48C11.07 1.84 12.93 1.84 14.07 2.48L19.41 5.44002C20.62 6.11002 21.61 7.79 21.61 9.17Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M23 18C23 18.75 22.79 19.46 22.42 20.06C22.21 20.42 21.94 20.74 21.63 21C20.93 21.63 20.01 22 19 22C17.54 22 16.27 21.22 15.58 20.06C15.21 19.46 15 18.75 15 18C15 16.74 15.58 15.61 16.5 14.88C17.19 14.33 18.06 14 19 14C21.21 14 23 15.79 23 18Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M20.07 19.0399L17.95 16.9299\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M20.05 16.96L17.93 19.0699\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:box-search": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M3.17004 7.43994L12 12.5499L20.77 7.46991\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M12 21.6099V12.5399\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M21.61 12.83V9.17C21.61 7.79 20.62 6.11002 19.41 5.44002L14.07 2.48C12.93 1.84 11.07 1.84 9.92999 2.48L4.59 5.44002C3.38 6.11002 2.39001 7.79 2.39001 9.17V14.83C2.39001 16.21 3.38 17.89 4.59 18.56L9.92999 21.52C10.5 21.84 11.25 22 12 22C12.75 22 13.5 21.84 14.07 21.52\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M19.2 21.4C20.9673 21.4 22.4 19.9673 22.4 18.2C22.4 16.4327 20.9673 15 19.2 15C17.4327 15 16 16.4327 16 18.2C16 19.9673 17.4327 21.4 19.2 21.4Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M23 22L22 21\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:building": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M12.5 22.0001H4.07997C2.91997 22.0001 1.96997 21.0701 1.96997 19.9301V5.09011C1.96997 2.47011 3.91997 1.2801 6.30997 2.4501L10.75 4.63011C11.71 5.10011 12.5 6.3501 12.5 7.4101V22.0001Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M21.97 15.0602V18.8402C21.97 21.0002 20.97 22.0002 18.81 22.0002H12.5V10.4202L12.97 10.5202L17.47 11.5302L19.5 11.9802C20.82 12.2702 21.9 12.9502 21.96 14.8702C21.97 14.9302 21.97 14.9902 21.97 15.0602Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M5.5 9H8.97\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M5.5 13H8.97\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M17.47 11.53V14.75C17.47 15.99 16.46 17 15.22 17C13.98 17 12.97 15.99 12.97 14.75V10.52L17.47 11.53Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M21.96 14.87C21.9 16.05 20.92 17 19.72 17C18.48 17 17.47 15.99 17.47 14.75V11.53L19.5 11.98C20.82 12.27 21.9 12.95 21.96 14.87Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:buildings": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M13 22H5C3 22 2 21 2 19V11C2 9 3 8 5 8H10V19C10 21 11 22 13 22Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M10.11 4C10.03 4.3 10 4.63 10 5V8H5V6C5 4.9 5.9 4 7 4H10.11Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M14 8V13\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M18 8V13\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M17 17H15C14.45 17 14 17.45 14 18V22H18V18C18 17.45 17.55 17 17 17Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M6 13V17\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M10 19V5C10 3 11 2 13 2H19C21 2 22 3 22 5V19C22 21 21 22 19 22H13C11 22 10 21 10 19Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:calendar": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M8 2V5\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M16 2V5\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M3.5 9.08997H20.5\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M21 8.5V17C21 20 19.5 22 16 22H8C4.5 22 3 20 3 17V8.5C3 5.5 4.5 3.5 8 3.5H16C19.5 3.5 21 5.5 21 8.5Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M15.6947 13.7H15.7037\" stroke=\"#292D32\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M15.6947 16.7H15.7037\" stroke=\"#292D32\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M11.9955 13.7H12.0045\" stroke=\"#292D32\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M11.9955 16.7H12.0045\" stroke=\"#292D32\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.29431 13.7H8.30329\" stroke=\"#292D32\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.29431 16.7H8.30329\" stroke=\"#292D32\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:call": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M21.97 18.33C21.97 18.69 21.89 19.06 21.72 19.42C21.55 19.78 21.33 20.12 21.04 20.44C20.55 20.98 20.01 21.37 19.4 21.62C18.8 21.87 18.15 22 17.45 22C16.43 22 15.34 21.76 14.19 21.27C13.04 20.78 11.89 20.12 10.75 19.29C9.6 18.45 8.51 17.52 7.47 16.49C6.44 15.45 5.51 14.36 4.68 13.22C3.86 12.08 3.2 10.94 2.72 9.81C2.24 8.67 2 7.58 2 6.54C2 5.86 2.12 5.21 2.36 4.61C2.6 4 2.98 3.44 3.51 2.94C4.15 2.31 4.85 2 5.59 2C5.87 2 6.15 2.06 6.4 2.18C6.66 2.3 6.89 2.48 7.07 2.74L9.39 6.01C9.57 6.26 9.7 6.49 9.79 6.71C9.88 6.92 9.93 7.13 9.93 7.32C9.93 7.56 9.86 7.8 9.72 8.03C9.59 8.26 9.4 8.5 9.16 8.74L8.4 9.53C8.29 9.64 8.24 9.77 8.24 9.93C8.24 10.01 8.25 10.08 8.27 10.16C8.3 10.24 8.33 10.3 8.35 10.36C8.53 10.69 8.84 11.12 9.28 11.64C9.73 12.16 10.21 12.69 10.73 13.22C11.27 13.75 11.79 14.24 12.32 14.69C12.84 15.13 13.27 15.43 13.61 15.61C13.66 15.63 13.72 15.66 13.79 15.69C13.87 15.72 13.95 15.73 14.04 15.73C14.21 15.73 14.34 15.67 14.45 15.56L15.21 14.81C15.46 14.56 15.7 14.37 15.93 14.25C16.16 14.11 16.39 14.04 16.64 14.04C16.83 14.04 17.03 14.08 17.25 14.17C17.47 14.26 17.7 14.39 17.95 14.56L21.26 16.91C21.52 17.09 21.7 17.3 21.81 17.55C21.91 17.8 21.97 18.05 21.97 18.33Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\"/>\n</svg>\n",
|
||||
"vuesax-linear:card": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M2 8.50488H22\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M6 16.5049H8\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M10.5 16.5049H14.5\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M6.44 3.50488H17.55C21.11 3.50488 22 4.38488 22 7.89488V16.1049C22 19.6149 21.11 20.4949 17.56 20.4949H6.44C2.89 20.5049 2 19.6249 2 16.1149V7.89488C2 4.38488 2.89 3.50488 6.44 3.50488Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:card-receive": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M2 8.5H14.5\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M6 16.5H8\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M10.5 16.5H14.5\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M22 14.03V16.11C22 19.62 21.11 20.5 17.56 20.5H6.44C2.89 20.5 2 19.62 2 16.11V7.89C2 4.38 2.89 3.5 6.44 3.5H14.5\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M20 3.5V9.5L22 7.5\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M20 9.5L18 7.5\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:category": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M5 10H7C9 10 10 9 10 7V5C10 3 9 2 7 2H5C3 2 2 3 2 5V7C2 9 3 10 5 10Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M17 10H19C21 10 22 9 22 7V5C22 3 21 2 19 2H17C15 2 14 3 14 5V7C14 9 15 10 17 10Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M17 22H19C21 22 22 21 22 19V17C22 15 21 14 19 14H17C15 14 14 15 14 17V19C14 21 15 22 17 22Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M5 22H7C9 22 10 21 10 19V17C10 15 9 14 7 14H5C3 14 2 15 2 17V19C2 21 3 22 5 22Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:chart-2": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M9 22H15C20 22 22 20 22 15V9C22 4 20 2 15 2H9C4 2 2 4 2 9V15C2 20 4 22 9 22Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M15.5 18.5C16.6 18.5 17.5 17.6 17.5 16.5V7.5C17.5 6.4 16.6 5.5 15.5 5.5C14.4 5.5 13.5 6.4 13.5 7.5V16.5C13.5 17.6 14.39 18.5 15.5 18.5Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.5 18.5C9.6 18.5 10.5 17.6 10.5 16.5V13C10.5 11.9 9.6 11 8.5 11C7.4 11 6.5 11.9 6.5 13V16.5C6.5 17.6 7.39 18.5 8.5 18.5Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:clipboard-close": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M14 16.16L10.04 12.2\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M13.96 12.24L10 16.2\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M10 6H14C16 6 16 5 16 4C16 2 15 2 14 2H10C9 2 8 2 8 4C8 6 9 6 10 6Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M16 4.02002C19.33 4.20002 21 5.43002 21 10V16C21 20 20 22 15 22H9C4 22 3 20 3 16V10C3 5.44002 4.67 4.20002 8 4.02002\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:clock": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M12 9.66003V12.45L13.4 13.85\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M5.5 12C5.5 8.41 8.41 5.5 12 5.5C15.59 5.5 18.5 8.41 18.5 12C18.5 14.08 17.52 15.94 16 17.13H15.99C14.89 17.99 13.51 18.5 12 18.5C10.51 18.5 9.14 18 8.04 17.15H8.03C6.49 15.96 5.5 14.1 5.5 12Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.03003 17.15H8.04003C9.14003 18 10.51 18.5 12 18.5C13.51 18.5 14.89 17.99 15.99 17.13H16L15.49 19.6C15 21.5 13.9 22 12.55 22H11.46C10.11 22 9.00003 21.5 8.52003 19.59L8.03003 17.15Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.03003 6.85H8.04003C9.14003 6 10.51 5.5 12 5.5C13.51 5.5 14.89 6.01 15.99 6.87H16L15.49 4.4C15 2.5 13.9 2 12.55 2H11.46C10.11 2 9.00003 2.5 8.52003 4.41L8.03003 6.85Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:close-circle": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M12 22C17.5 22 22 17.5 22 12C22 6.5 17.5 2 12 2C6.5 2 2 6.5 2 12C2 17.5 6.5 22 12 22Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M9.16998 14.83L14.83 9.17004\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M14.83 14.83L9.16998 9.17004\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:cpu": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M9.6 20H14.4C18.4 20 20 18.4 20 14.4V9.6C20 5.6 18.4 4 14.4 4H9.6C5.6 4 4 5.6 4 9.6V14.4C4 18.4 5.6 20 9.6 20Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M10.5 17H13.5C16 17 17 16 17 13.5V10.5C17 8 16 7 13.5 7H10.5C8 7 7 8 7 10.5V13.5C7 16 8 17 10.5 17Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.01001 4V2\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M12 4V2\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M16 4V2\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M20 8H22\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M20 12H22\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M20 16H22\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M16 20V22\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M12.01 20V22\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.01001 20V22\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M2 8H4\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M2 12H4\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M2 16H4\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:diamonds": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.29 2.15002H7.69995C5.99995 2.15002 5.24995 3.00002 4.78995 4.04002L2.22995 9.80002C1.76995 10.84 2.01995 12.39 2.78995 13.23L9.64995 20.77C10.95 22.19 13.07 22.19 14.36 20.77L21.21 13.22C21.98 12.37 22.23 10.83 21.76 9.79002L19.2 4.03002C18.74 3.00002 17.99 2.15002 16.29 2.15002Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M3.5 8H20.5\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:document-text": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M21 7V17C21 20 19.5 22 16 22H8C4.5 22 3 20 3 17V7C3 4 4.5 2 8 2H16C19.5 2 21 4 21 7Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M14.5 4.5V6.5C14.5 7.6 15.4 8.5 16.5 8.5H18.5\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8 13H12\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8 17H16\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:edit-2": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M13.26 3.59997L5.04997 12.29C4.73997 12.62 4.43997 13.27 4.37997 13.72L4.00997 16.96C3.87997 18.13 4.71997 18.93 5.87997 18.73L9.09997 18.18C9.54997 18.1 10.18 17.77 10.49 17.43L18.7 8.73997C20.12 7.23997 20.76 5.52997 18.55 3.43997C16.35 1.36997 14.68 2.09997 13.26 3.59997Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M11.89 5.05005C12.32 7.81005 14.56 9.92005 17.34 10.2\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M3 22H21\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:element-3": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M22 8.52V3.98C22 2.57 21.36 2 19.77 2H15.73C14.14 2 13.5 2.57 13.5 3.98V8.51C13.5 9.93 14.14 10.49 15.73 10.49H19.77C21.36 10.5 22 9.93 22 8.52Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M22 19.77V15.73C22 14.14 21.36 13.5 19.77 13.5H15.73C14.14 13.5 13.5 14.14 13.5 15.73V19.77C13.5 21.36 14.14 22 15.73 22H19.77C21.36 22 22 21.36 22 19.77Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M10.5 8.52V3.98C10.5 2.57 9.86 2 8.27 2H4.23C2.64 2 2 2.57 2 3.98V8.51C2 9.93 2.64 10.49 4.23 10.49H8.27C9.86 10.5 10.5 9.93 10.5 8.52Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M10.5 19.77V15.73C10.5 14.14 9.86 13.5 8.27 13.5H4.23C2.64 13.5 2 14.14 2 15.73V19.77C2 21.36 2.64 22 4.23 22H8.27C9.86 22 10.5 21.36 10.5 19.77Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:eye": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M15.58 12C15.58 13.98 13.98 15.58 12 15.58C10.02 15.58 8.42004 13.98 8.42004 12C8.42004 10.02 10.02 8.42004 12 8.42004C13.98 8.42004 15.58 10.02 15.58 12Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M12 20.27C15.53 20.27 18.82 18.19 21.11 14.59C22.01 13.18 22.01 10.81 21.11 9.39997C18.82 5.79997 15.53 3.71997 12 3.71997C8.46997 3.71997 5.17997 5.79997 2.88997 9.39997C1.98997 10.81 1.98997 13.18 2.88997 14.59C5.17997 18.19 8.46997 20.27 12 20.27Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:eye-slash": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M14.53 9.47004L9.47004 14.53C8.82004 13.88 8.42004 12.99 8.42004 12C8.42004 10.02 10.02 8.42004 12 8.42004C12.99 8.42004 13.88 8.82004 14.53 9.47004Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M17.82 5.76998C16.07 4.44998 14.07 3.72998 12 3.72998C8.46997 3.72998 5.17997 5.80998 2.88997 9.40998C1.98997 10.82 1.98997 13.19 2.88997 14.6C3.67997 15.84 4.59997 16.91 5.59997 17.77\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.42004 19.5301C9.56004 20.0101 10.77 20.2701 12 20.2701C15.53 20.2701 18.82 18.1901 21.11 14.5901C22.01 13.1801 22.01 10.8101 21.11 9.40005C20.78 8.88005 20.42 8.39005 20.05 7.93005\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M15.5099 12.7C15.2499 14.11 14.0999 15.26 12.6899 15.52\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M9.47 14.53L2 22\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M22 2L14.53 9.47\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:filter": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M5.40002 2.09998H18.6C19.7 2.09998 20.6 2.99998 20.6 4.09998V6.29998C20.6 7.09998 20.1 8.09998 19.6 8.59998L15.3 12.4C14.7 12.9 14.3 13.9 14.3 14.7V19C14.3 19.6 13.9 20.4 13.4 20.7L12 21.6C10.7 22.4 8.90002 21.5 8.90002 19.9V14.6C8.90002 13.9 8.50002 13 8.10002 12.5L4.30002 8.49998C3.80002 7.99998 3.40002 7.09998 3.40002 6.49998V4.19998C3.40002 2.99998 4.30002 2.09998 5.40002 2.09998Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M10.93 2.09998L6 9.99998\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:flash": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M9.31993 13.28H12.4099V20.48C12.4099 21.54 13.7299 22.04 14.4299 21.24L21.9999 12.64C22.6599 11.89 22.1299 10.72 21.1299 10.72H18.0399V3.51997C18.0399 2.45997 16.7199 1.95997 16.0199 2.75997L8.44994 11.36C7.79994 12.11 8.32993 13.28 9.31993 13.28Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.5 4H1.5\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M7.5 20H1.5\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M4.5 12H1.5\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:headphone": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M5.46005 18.49V15.57C5.46005 14.6 6.22005 13.73 7.30005 13.73C8.27005 13.73 9.14005 14.49 9.14005 15.57V18.38C9.14005 20.33 7.52005 21.9501 5.57005 21.9501C3.62005 21.9501 2.00005 20.32 2.00005 18.38V12.22C1.89005 6.60005 6.33005 2.05005 11.95 2.05005C17.57 2.05005 22 6.60005 22 12.11V18.2701C22 20.2201 20.38 21.84 18.43 21.84C16.48 21.84 14.86 20.2201 14.86 18.2701V15.46C14.86 14.49 15.62 13.62 16.7 13.62C17.67 13.62 18.54 14.38 18.54 15.46V18.49\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:home": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M12 18V15\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M10.07 2.81997L3.14002 8.36997C2.36002 8.98997 1.86002 10.3 2.03002 11.28L3.36002 19.24C3.60002 20.66 4.96002 21.81 6.40002 21.81H17.6C19.03 21.81 20.4 20.65 20.64 19.24L21.97 11.28C22.13 10.3 21.63 8.98997 20.86 8.36997L13.93 2.82997C12.86 1.96997 11.13 1.96997 10.07 2.81997Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:info-circle": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M12 22C17.5 22 22 17.5 22 12C22 6.5 17.5 2 12 2C6.5 2 2 6.5 2 12C2 17.5 6.5 22 12 22Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M12 8V13\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M11.9945 16H12.0035\" stroke=\"#292D32\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:lamp-on": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M8.30011 18.0399V16.8799C6.00011 15.4899 4.11011 12.7799 4.11011 9.89993C4.11011 4.94993 8.66011 1.06993 13.8001 2.18993C16.0601 2.68993 18.0401 4.18993 19.0701 6.25993C21.1601 10.4599 18.9601 14.9199 15.7301 16.8699V18.0299C15.7301 18.3199 15.8401 18.9899 14.7701 18.9899H9.26011C8.16011 18.9999 8.30011 18.5699 8.30011 18.0399Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.5 22C10.79 21.35 13.21 21.35 15.5 22\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:location": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M12 13.4299C13.7231 13.4299 15.12 12.0331 15.12 10.3099C15.12 8.58681 13.7231 7.18994 12 7.18994C10.2769 7.18994 8.88 8.58681 8.88 10.3099C8.88 12.0331 10.2769 13.4299 12 13.4299Z\" stroke=\"#292D32\" stroke-width=\"1.5\"/>\n<path d=\"M3.62001 8.49C5.59001 -0.169998 18.42 -0.159997 20.38 8.5C21.53 13.58 18.37 17.88 15.6 20.54C13.59 22.48 10.41 22.48 8.39001 20.54C5.63001 17.88 2.47001 13.57 3.62001 8.49Z\" stroke=\"#292D32\" stroke-width=\"1.5\"/>\n</svg>\n",
|
||||
"vuesax-linear:lock": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M6 10V8C6 4.69 7 2 12 2C17 2 18 4.69 18 8V10\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M17 22H7C3 22 2 21 2 17V15C2 11 3 10 7 10H17C21 10 22 11 22 15V17C22 21 21 22 17 22Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M15.9965 16H16.0054\" stroke=\"#292D32\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M11.9955 16H12.0045\" stroke=\"#292D32\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M7.99451 16H8.00349\" stroke=\"#292D32\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:logout": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M8.90002 7.55999C9.21002 3.95999 11.06 2.48999 15.11 2.48999H15.24C19.71 2.48999 21.5 4.27999 21.5 8.74999V15.27C21.5 19.74 19.71 21.53 15.24 21.53H15.11C11.09 21.53 9.24002 20.08 8.91002 16.54\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M15 12H3.62\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M5.85 8.6499L2.5 11.9999L5.85 15.3499\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:menu": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M3 7H21\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n<path d=\"M3 12H21\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n<path d=\"M3 17H21\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:message": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M8.5 19H8C4 19 2 18 2 13V8C2 4 4 2 8 2H16C20 2 22 4 22 8V13C22 17 20 19 16 19H15.5C15.19 19 14.89 19.15 14.7 19.4L13.2 21.4C12.54 22.28 11.46 22.28 10.8 21.4L9.3 19.4C9.14 19.18 8.77 19 8.5 19Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M15.9965 11H16.0054\" stroke=\"#292D32\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M11.9955 11H12.0045\" stroke=\"#292D32\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M7.99451 11H8.00349\" stroke=\"#292D32\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:mobile": "<svg width=\"18\" height=\"22\" viewBox=\"0 0 18 22\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 6V16C17 20 16 21 12 21H6C2 21 1 20 1 16V6C1 2 2 1 6 1H12C16 1 17 2 17 6Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M11 4.5H7\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.99995 18.1C9.85599 18.1 10.55 17.406 10.55 16.55C10.55 15.694 9.85599 15 8.99995 15C8.14391 15 7.44995 15.694 7.44995 16.55C7.44995 17.406 8.14391 18.1 8.99995 18.1Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:money-4": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 20.5H7C4 20.5 2 19 2 15.5V8.5C2 5 4 3.5 7 3.5H17C20 3.5 22 5 22 8.5V15.5C22 19 20 20.5 17 20.5Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.5 6H7C5.62 6 4.5 7.12 4.5 8.5V10\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M15.5 6H17C18.38 6 19.5 7.12 19.5 8.5V10\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.5 18H7C5.62 18 4.5 16.88 4.5 15.5V14\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M15.5 18H17C18.38 18 19.5 16.88 19.5 15.5V14\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:monitor": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M6.44 2H17.55C21.11 2 22 2.89 22 6.44V12.77C22 16.33 21.11 17.21 17.56 17.21H6.44C2.89 17.22 2 16.33 2 12.78V6.44C2 2.89 2.89 2 6.44 2Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M12 17.22V22\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M2 13H22\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M7.5 22H16.5\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:moon": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M2.03009 12.42C2.39009 17.57 6.76009 21.76 11.9901 21.99C15.6801 22.15 18.9801 20.43 20.9601 17.72C21.7801 16.61 21.3401 15.87 19.9701 16.12C19.3001 16.24 18.6101 16.29 17.8901 16.26C13.0001 16.06 9.00009 11.97 8.98009 7.13996C8.97009 5.83996 9.24009 4.60996 9.73009 3.48996C10.2701 2.24996 9.62009 1.65996 8.37009 2.18996C4.41009 3.85996 1.70009 7.84996 2.03009 12.42Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:more": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M5 10C3.9 10 3 10.9 3 12C3 13.1 3.9 14 5 14C6.1 14 7 13.1 7 12C7 10.9 6.1 10 5 10Z\" stroke=\"#292D32\" stroke-width=\"1.5\"/>\n<path d=\"M19 10C17.9 10 17 10.9 17 12C17 13.1 17.9 14 19 14C20.1 14 21 13.1 21 12C21 10.9 20.1 10 19 10Z\" stroke=\"#292D32\" stroke-width=\"1.5\"/>\n<path d=\"M12 10C10.9 10 10 10.9 10 12C10 13.1 10.9 14 12 14C13.1 14 14 13.1 14 12C14 10.9 13.1 10 12 10Z\" stroke=\"#292D32\" stroke-width=\"1.5\"/>\n</svg>\n",
|
||||
"vuesax-linear:note-remove": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M7 14H12\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M7 5.95996L3.25 2.20996\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M6.96002 2.25L3.21002 6\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M7 10H15\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M10 2H16C19.33 2.18 21 3.41 21 7.99V16\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M3 9.01001V15.98C3 19.99 4 22 9 22H12C12.17 22 14.84 22 15 22\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M21 16L15 22V19C15 17 16 16 18 16H21Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:notification": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M12.02 2.90991C8.70997 2.90991 6.01997 5.59991 6.01997 8.90991V11.7999C6.01997 12.4099 5.75997 13.3399 5.44997 13.8599L4.29997 15.7699C3.58997 16.9499 4.07997 18.2599 5.37997 18.6999C9.68997 20.1399 14.34 20.1399 18.65 18.6999C19.86 18.2999 20.39 16.8699 19.73 15.7699L18.58 13.8599C18.28 13.3399 18.02 12.4099 18.02 11.7999V8.90991C18.02 5.60991 15.32 2.90991 12.02 2.90991Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\"/>\n<path d=\"M13.87 3.19994C13.56 3.10994 13.24 3.03994 12.91 2.99994C11.95 2.87994 11.03 2.94994 10.17 3.19994C10.46 2.45994 11.18 1.93994 12.02 1.93994C12.86 1.93994 13.58 2.45994 13.87 3.19994Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M15.02 19.0601C15.02 20.7101 13.67 22.0601 12.02 22.0601C11.2 22.0601 10.44 21.7201 9.90002 21.1801C9.36002 20.6401 9.02002 19.8801 9.02002 19.0601\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\"/>\n</svg>\n",
|
||||
"vuesax-linear:pen-add": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M21.19 8.0399L18.01 4.85991C16.81 3.65991 15.16 3.71993 14.35 5.00993L12.58 7.80992L18.25 13.4799L21.05 11.7099C22.26 10.9399 22.33 9.1699 21.19 8.0399Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M18.25 13.47L18.49 17.59C18.72 19.89 17.92 20.69 15.74 20.95L7.02 21.98C5.18 22.19 3.86 20.87 4.08 19.04L5.06 10.76\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M12.58 7.80994L10.83 7.69995\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M5.28 20.7799L8.46001 17.5898\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M11 6.5C11 6.91 10.94 7.32001 10.83 7.70001C10.72 8.10001 10.56 8.47001 10.35 8.82001C10.11 9.22001 9.81001 9.58 9.46001 9.88C8.67001 10.58 7.64 11 6.5 11C5.99 11 5.51 10.92 5.06 10.76C4.04 10.42 3.18999 9.72001 2.64999 8.82001C2.23999 8.14001 2 7.34 2 6.5C2 5.08 2.65 3.80999 3.69 2.98999C4.46 2.36999 5.44 2 6.5 2C8.99 2 11 4.01 11 6.5Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M6.52 8.18005V4.82007\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.16 6.5H4.8\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:people": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M18 7.16C17.94 7.15 17.87 7.15 17.81 7.16C16.43 7.11 15.33 5.98 15.33 4.58C15.33 3.15 16.48 2 17.91 2C19.34 2 20.49 3.16 20.49 4.58C20.48 5.98 19.38 7.11 18 7.16Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M16.9699 14.44C18.3399 14.67 19.8499 14.43 20.9099 13.72C22.3199 12.78 22.3199 11.24 20.9099 10.3C19.8399 9.59004 18.3099 9.35003 16.9399 9.59003\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M5.96998 7.16C6.02998 7.15 6.09998 7.15 6.15998 7.16C7.53998 7.11 8.63998 5.98 8.63998 4.58C8.63998 3.15 7.48998 2 6.05998 2C4.62998 2 3.47998 3.16 3.47998 4.58C3.48998 5.98 4.58998 7.11 5.96998 7.16Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M6.99994 14.44C5.62994 14.67 4.11994 14.43 3.05994 13.72C1.64994 12.78 1.64994 11.24 3.05994 10.3C4.12994 9.59004 5.65994 9.35003 7.02994 9.59003\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M12 14.63C11.94 14.62 11.87 14.62 11.81 14.63C10.43 14.58 9.32996 13.45 9.32996 12.05C9.32996 10.62 10.48 9.46997 11.91 9.46997C13.34 9.46997 14.49 10.63 14.49 12.05C14.48 13.45 13.38 14.59 12 14.63Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M9.08997 17.78C7.67997 18.72 7.67997 20.26 9.08997 21.2C10.69 22.27 13.31 22.27 14.91 21.2C16.32 20.26 16.32 18.72 14.91 17.78C13.32 16.72 10.69 16.72 9.08997 17.78Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:personalcard": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 21H7C3 21 2 20 2 16V8C2 4 3 3 7 3H17C21 3 22 4 22 8V16C22 20 21 21 17 21Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M14 8H19\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M15 12H19\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M17 16H19\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.49994 11.2899C9.49958 11.2899 10.3099 10.4796 10.3099 9.47992C10.3099 8.48029 9.49958 7.66992 8.49994 7.66992C7.50031 7.66992 6.68994 8.48029 6.68994 9.47992C6.68994 10.4796 7.50031 11.2899 8.49994 11.2899Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M12 16.33C11.86 14.88 10.71 13.74 9.26 13.61C8.76 13.56 8.25 13.56 7.74 13.61C6.29 13.75 5.14 14.88 5 16.33\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:printer": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M7.25 7H16.75V5C16.75 3 16 2 13.75 2H10.25C8 2 7.25 3 7.25 5V7Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M16 15V19C16 21 15 22 13 22H11C9 22 8 21 8 19V15H16Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M21 10V15C21 17 20 18 18 18H16V15H8V18H6C4 18 3 17 3 15V10C3 8 4 7 6 7H18C20 7 21 8 21 10Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M17 15H15.79H7\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M7 11H10\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:profile-2user": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M9.16006 10.87C9.06006 10.86 8.94006 10.86 8.83006 10.87C6.45006 10.79 4.56006 8.84 4.56006 6.44C4.56006 3.99 6.54006 2 9.00006 2C11.4501 2 13.4401 3.99 13.4401 6.44C13.4301 8.84 11.5401 10.79 9.16006 10.87Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M16.41 4C18.35 4 19.91 5.57 19.91 7.5C19.91 9.39 18.41 10.93 16.54 11C16.46 10.99 16.37 10.99 16.28 11\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M4.15997 14.56C1.73997 16.18 1.73997 18.82 4.15997 20.43C6.90997 22.27 11.42 22.27 14.17 20.43C16.59 18.81 16.59 16.17 14.17 14.56C11.43 12.73 6.91997 12.73 4.15997 14.56Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M18.3401 20C19.0601 19.85 19.7401 19.56 20.3001 19.13C21.8601 17.96 21.8601 16.03 20.3001 14.86C19.7501 14.44 19.0801 14.16 18.3701 14\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:profile-circle": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M12.12 12.78C12.05 12.77 11.96 12.77 11.88 12.78C10.12 12.72 8.71997 11.28 8.71997 9.50998C8.71997 7.69998 10.18 6.22998 12 6.22998C13.81 6.22998 15.28 7.69998 15.28 9.50998C15.27 11.28 13.88 12.72 12.12 12.78Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M18.74 19.3801C16.96 21.0101 14.6 22.0001 12 22.0001C9.40001 22.0001 7.04001 21.0101 5.26001 19.3801C5.36001 18.4401 5.96001 17.5201 7.03001 16.8001C9.77001 14.9801 14.25 14.9801 16.97 16.8001C18.04 17.5201 18.64 18.4401 18.74 19.3801Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:receipt-search": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M20.5 11.3V7.04001C20.5 3.01001 19.56 2 15.78 2H8.22C4.44 2 3.5 3.01001 3.5 7.04001V18.3C3.5 20.96 4.96001 21.59 6.73001 19.69L6.73999 19.68C7.55999 18.81 8.80999 18.88 9.51999 19.83L10.53 21.18\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M18.2 21.4C19.9673 21.4 21.4 19.9673 21.4 18.2C21.4 16.4327 19.9673 15 18.2 15C16.4327 15 15 16.4327 15 18.2C15 19.9673 16.4327 21.4 18.2 21.4Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M22 22L21 21\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8 7H16\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M9 11H15\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:search-normal": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M11 20C15.9706 20 20 15.9706 20 11C20 6.02944 15.9706 2 11 2C6.02944 2 2 6.02944 2 11C2 15.9706 6.02944 20 11 20Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M18.9299 20.6898C19.4599 22.2898 20.6699 22.4498 21.5999 21.0498C22.4499 19.7698 21.8899 18.7198 20.3499 18.7198C19.2099 18.7098 18.5699 19.5998 18.9299 20.6898Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:search-status": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M14 5H20\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M14 8H17\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M21 11.5C21 16.75 16.75 21 11.5 21C6.25 21 2 16.75 2 11.5C2 6.25 6.25 2 11.5 2\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M22 22L20 20\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:setting-2": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M2 12.8799V11.1199C2 10.0799 2.85 9.21994 3.9 9.21994C5.71 9.21994 6.45 7.93994 5.54 6.36994C5.02 5.46994 5.33 4.29994 6.24 3.77994L7.97 2.78994C8.76 2.31994 9.78 2.59994 10.25 3.38994L10.36 3.57994C11.26 5.14994 12.74 5.14994 13.65 3.57994L13.76 3.38994C14.23 2.59994 15.25 2.31994 16.04 2.78994L17.77 3.77994C18.68 4.29994 18.99 5.46994 18.47 6.36994C17.56 7.93994 18.3 9.21994 20.11 9.21994C21.15 9.21994 22.01 10.0699 22.01 11.1199V12.8799C22.01 13.9199 21.16 14.7799 20.11 14.7799C18.3 14.7799 17.56 16.0599 18.47 17.6299C18.99 18.5399 18.68 19.6999 17.77 20.2199L16.04 21.2099C15.25 21.6799 14.23 21.3999 13.76 20.6099L13.65 20.4199C12.75 18.8499 11.27 18.8499 10.36 20.4199L10.25 20.6099C9.78 21.3999 8.76 21.6799 7.97 21.2099L6.24 20.2199C5.33 19.6999 5.02 18.5299 5.54 17.6299C6.45 16.0599 5.71 14.7799 3.9 14.7799C2.85 14.7799 2 13.9199 2 12.8799Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:shopping-bag": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M8.39999 6.5H15.6C19 6.5 19.34 8.09 19.57 10.03L20.47 17.53C20.76 19.99 20 22 16.5 22H7.50999C3.99999 22 3.23999 19.99 3.53999 17.53L4.44 10.03C4.66 8.09 4.99999 6.5 8.39999 6.5Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8 8V4.5C8 3 9 2 10.5 2H13.5C15 2 16 3 16 4.5V8\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M20.41 17.03H8\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:sms": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 20.5H7C4 20.5 2 19 2 15.5V8.5C2 5 4 3.5 7 3.5H17C20 3.5 22 5 22 8.5V15.5C22 19 20 20.5 17 20.5Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M17 9L13.87 11.5C12.84 12.32 11.15 12.32 10.12 11.5L7 9\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:sun-1": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M12 18.5C15.5899 18.5 18.5 15.5899 18.5 12C18.5 8.41015 15.5899 5.5 12 5.5C8.41015 5.5 5.5 8.41015 5.5 12C5.5 15.5899 8.41015 18.5 12 18.5Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M19.14 19.14L19.01 19.01M19.01 4.99L19.14 4.86L19.01 4.99ZM4.86 19.14L4.99 19.01L4.86 19.14ZM12 2.08V2V2.08ZM12 22V21.92V22ZM2.08 12H2H2.08ZM22 12H21.92H22ZM4.99 4.99L4.86 4.86L4.99 4.99Z\" stroke=\"#292D32\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:tick-circle": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M12 22C17.5 22 22 17.5 22 12C22 6.5 17.5 2 12 2C6.5 2 2 6.5 2 12C2 17.5 6.5 22 12 22Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M7.75 12L10.58 14.83L16.25 9.17004\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:timer-1": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M20.75 13.25C20.75 18.08 16.83 22 12 22C7.17 22 3.25 18.08 3.25 13.25C3.25 8.42 7.17 4.5 12 4.5C16.83 4.5 20.75 8.42 20.75 13.25Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M12 8V13\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M9 2H15\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:trash": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M21 5.97998C17.67 5.64998 14.32 5.47998 10.98 5.47998C9 5.47998 7.02 5.57998 5.04 5.77998L3 5.97998\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8.5 4.97L8.72 3.66C8.88 2.71 9 2 10.69 2H13.31C15 2 15.13 2.75 15.28 3.67L15.5 4.97\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M18.85 9.14001L18.2 19.21C18.09 20.78 18 22 15.21 22H8.79002C6.00002 22 5.91002 20.78 5.80002 19.21L5.15002 9.14001\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M10.33 16.5H13.66\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M9.5 12.5H14.5\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:truck-time": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M15 2V12C15 13.1 14.1 14 13 14H2V7.62C2.73 8.49 3.85003 9.03 5.09003 9C6.10003 8.98 7.01 8.59 7.69 7.94C8 7.68 8.26002 7.34999 8.46002 6.98999C8.82002 6.37999 9.02 5.65997 9 4.90997C8.97 3.73997 8.45001 2.71 7.64001 2H15Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M22 14V17C22 18.66 20.66 20 19 20H18C18 18.9 17.1 18 16 18C14.9 18 14 18.9 14 20H10C10 18.9 9.1 18 8 18C6.9 18 6 18.9 6 20H5C3.34 20 2 18.66 2 17V14H13C14.1 14 15 13.1 15 12V5H16.84C17.56 5 18.22 5.39001 18.58 6.01001L20.29 9H19C18.45 9 18 9.45 18 10V13C18 13.55 18.45 14 19 14H22Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M8 22C9.10457 22 10 21.1046 10 20C10 18.8954 9.10457 18 8 18C6.89543 18 6 18.8954 6 20C6 21.1046 6.89543 22 8 22Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M16 22C17.1046 22 18 21.1046 18 20C18 18.8954 17.1046 18 16 18C14.8954 18 14 18.8954 14 20C14 21.1046 14.8954 22 16 22Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M22 12V14H19C18.45 14 18 13.55 18 13V10C18 9.45 18.45 9 19 9H20.29L22 12Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M9 5C9 6.2 8.47001 7.27 7.64001 8C6.93001 8.62 6.01 9 5 9C2.79 9 1 7.21 1 5C1 3.74 1.58 2.61 2.5 1.88C3.19 1.33 4.06 1 5 1C7.21 1 9 2.79 9 5Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M5.25 3.75V5.25L4 6\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-miterlimit=\"10\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:verify": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M8.38 12L10.79 14.42L15.62 9.57996\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M10.75 2.44995C11.44 1.85995 12.57 1.85995 13.27 2.44995L14.85 3.80995C15.15 4.06995 15.71 4.27995 16.11 4.27995H17.81C18.87 4.27995 19.74 5.14995 19.74 6.20995V7.90995C19.74 8.29995 19.95 8.86995 20.21 9.16995L21.57 10.7499C22.16 11.4399 22.16 12.5699 21.57 13.2699L20.21 14.8499C19.95 15.1499 19.74 15.7099 19.74 16.1099V17.8099C19.74 18.8699 18.87 19.7399 17.81 19.7399H16.11C15.72 19.7399 15.15 19.9499 14.85 20.2099L13.27 21.5699C12.58 22.1599 11.45 22.1599 10.75 21.5699L9.17 20.2099C8.87 19.9499 8.31 19.7399 7.91 19.7399H6.18C5.12 19.7399 4.25 18.8699 4.25 17.8099V16.0999C4.25 15.7099 4.04 15.1499 3.79 14.8499L2.44 13.2599C1.86 12.5699 1.86 11.4499 2.44 10.7599L3.79 9.16995C4.04 8.86995 4.25 8.30995 4.25 7.91995V6.19995C4.25 5.13995 5.12 4.26995 6.18 4.26995H7.91C8.3 4.26995 8.87 4.05995 9.17 3.79995L10.75 2.44995Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:wallet": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M22 12V17C22 20 20 22 17 22H7C4 22 2 20 2 17V12C2 9.28 3.64 7.38 6.19 7.06C6.45 7.02 6.72 7 7 7H17C17.26 7 17.51 7.00999 17.75 7.04999C20.33 7.34999 22 9.26 22 12Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M17.7514 7.05C17.5114 7.01 17.2614 7.00001 17.0014 7.00001H7.00141C6.72141 7.00001 6.45141 7.02001 6.19141 7.06001C6.33141 6.78001 6.53141 6.52001 6.77141 6.28001L10.0214 3.02C11.3914 1.66 13.6114 1.66 14.9814 3.02L16.7314 4.79002C17.3714 5.42002 17.7114 6.22 17.7514 7.05Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M22 12.5H19C17.9 12.5 17 13.4 17 14.5C17 15.6 17.9 16.5 19 16.5H22\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
|
||||
"vuesax-linear:warning-2": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M12 7.75V13\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M21.08 8.58003V15.42C21.08 16.54 20.48 17.58 19.51 18.15L13.57 21.58C12.6 22.14 11.4 22.14 10.42 21.58L4.47998 18.15C3.50998 17.59 2.90997 16.55 2.90997 15.42V8.58003C2.90997 7.46003 3.50998 6.41999 4.47998 5.84999L10.42 2.42C11.39 1.86 12.59 1.86 13.57 2.42L19.51 5.84999C20.48 6.41999 21.08 7.45003 21.08 8.58003Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M12 16.2V16.2999\" stroke=\"#292D32\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n"
|
||||
}
|
||||
@@ -1,20 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 20 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M18 8a6 6 0 10-12 0c0 7-3 9-3 9h18s-3-2-3-9" />
|
||||
<path d="M13.73 21a2 2 0 01-3.46 0" />
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:notification"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 24 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="size" :height="size" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M13 2 5 14h6l-1 8 8-12h-6l1-8Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:flash"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 24 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="size" :height="size" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M4 21V8l2-4h12l2 4v13H4Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path d="M4 11h16" stroke="currentColor" stroke-width="1.75" />
|
||||
<path
|
||||
d="M9 7V5a3 3 0 0 1 6 0v2"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:buildings"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,30 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.75 },
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M3 4h2l1.5 9h11L19 4h2"
|
||||
stroke="currentColor"
|
||||
:stroke-width="strokeWidth"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<circle cx="9" cy="19" r="1.5" fill="currentColor" />
|
||||
<circle cx="17" cy="19" r="1.5" fill="currentColor" />
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:bag-2"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 32 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="size" :height="size" viewBox="0 0 32 32" fill="none" aria-hidden="true">
|
||||
<rect
|
||||
x="4"
|
||||
y="8"
|
||||
width="24"
|
||||
height="16"
|
||||
rx="3"
|
||||
fill="#d4edda"
|
||||
stroke="#28a745"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<circle cx="16" cy="16" r="6" fill="#28a745" />
|
||||
<path
|
||||
d="M13 16l2 2 4-4"
|
||||
stroke="#fff"
|
||||
stroke-width="1.75"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:money-4"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 32 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="size" :height="size" viewBox="0 0 32 32" fill="none" aria-hidden="true">
|
||||
<rect
|
||||
x="3"
|
||||
y="7"
|
||||
width="26"
|
||||
height="18"
|
||||
rx="3"
|
||||
fill="#d4edda"
|
||||
stroke="#28a745"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<circle cx="16" cy="16" r="5" stroke="#28a745" stroke-width="1.5" />
|
||||
<path d="M16 13v6M13 16h6" stroke="#28a745" stroke-width="1.5" stroke-linecap="round" />
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:card"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 24 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="size" :height="size" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<rect
|
||||
x="4"
|
||||
y="3"
|
||||
width="16"
|
||||
height="18"
|
||||
rx="2"
|
||||
fill="#e8eef4"
|
||||
stroke="#1d3557"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<path d="M8 8h8M8 12h8M8 16h5" stroke="#1d3557" stroke-width="1.25" stroke-linecap="round" />
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:category"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 32 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="size" :height="size" viewBox="0 0 32 32" fill="none" aria-hidden="true">
|
||||
<rect
|
||||
x="4"
|
||||
y="6"
|
||||
width="24"
|
||||
height="20"
|
||||
rx="3"
|
||||
fill="#e8eef4"
|
||||
stroke="#1d3557"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<path d="M8 12h16M8 16h12M8 20h8" stroke="#1d3557" stroke-width="1.5" stroke-linecap="round" />
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:card-receive"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,29 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 22, strokeWidth: 1.75 },
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="12" cy="12" r="9" stroke="currentColor" :stroke-width="strokeWidth" />
|
||||
<path
|
||||
d="M8 12.5l2.5 2.5L16 9.5"
|
||||
stroke="currentColor"
|
||||
:stroke-width="strokeWidth"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:tick-circle"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,28 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 20, strokeWidth: 2 },
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M9 6l6 6-6 6"
|
||||
stroke="currentColor"
|
||||
:stroke-width="strokeWidth"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:arrow-right-1"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,28 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 18, strokeWidth: 2 },
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M15 6l-6 6 6 6"
|
||||
stroke="currentColor"
|
||||
:stroke-width="strokeWidth"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:arrow-left-1"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,75 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 64 },
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 64 64"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
class="clipboard-icon"
|
||||
>
|
||||
<rect
|
||||
x="14"
|
||||
y="8"
|
||||
width="36"
|
||||
height="48"
|
||||
rx="4"
|
||||
fill="#E8D5B5"
|
||||
stroke="#C4A574"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<rect
|
||||
x="24"
|
||||
y="4"
|
||||
width="16"
|
||||
height="8"
|
||||
rx="2"
|
||||
fill="#94A3B8"
|
||||
stroke="#64748B"
|
||||
stroke-width="1"
|
||||
/>
|
||||
<line
|
||||
x1="22"
|
||||
y1="24"
|
||||
x2="42"
|
||||
y2="24"
|
||||
stroke="#94A3B8"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<line
|
||||
x1="22"
|
||||
y1="32"
|
||||
x2="42"
|
||||
y2="32"
|
||||
stroke="#94A3B8"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<line
|
||||
x1="22"
|
||||
y1="40"
|
||||
x2="36"
|
||||
y2="40"
|
||||
stroke="#94A3B8"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:clipboard-close"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.clipboard-icon {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 28 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 28 28"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M14 4l2.5 5 5.5.8-4 3.9.9 5.5L14 16.8 8.1 19.2l.9-5.5-4-3.9 5.5-.8L14 4Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:diamonds"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,28 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 32 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M19.5 5.5l3 3-11 11-4 1 1-4 11-11z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path d="M17 8l3 3" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" />
|
||||
<path
|
||||
d="M7 25h18"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:edit-2"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -4,6 +4,7 @@ withDefaults(defineProps<{ size?: number }>(), { size: 72 })
|
||||
|
||||
<template>
|
||||
<svg
|
||||
class="icon-logo"
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 72 72"
|
||||
@@ -11,7 +12,17 @@ withDefaults(defineProps<{ size?: number }>(), { size: 72 })
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect x="14" y="14" width="28" height="28" rx="4" fill="currentColor" transform="rotate(12 28 28)" />
|
||||
<rect x="30" y="30" width="28" height="28" rx="4" fill="currentColor" transform="rotate(-12 44 44)" />
|
||||
<g fill="currentColor">
|
||||
<rect x="14" y="14" width="28" height="28" rx="4" transform="rotate(12 28 28)" />
|
||||
<rect x="30" y="30" width="28" height="28" rx="4" transform="rotate(-12 44 44)" />
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.icon-logo {
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 28 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 28 28"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M14 3c-1.5 0-2.5 1-2.5 2.2 0 .4.1.8.3 1.1-2 .5-3.5 2.2-3.5 4.3v1.2c0 .6.4 1 1 1h9.4c.6 0 1-.4 1-1v-1.2c0-2.1-1.5-3.8-3.5-4.3.2-.3.3-.7.3-1.1C16.5 4 15.5 3 14 3Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M8 12.8v8.5c0 1.2 1 2.2 2.2 2.2h7.6c1.2 0 2.2-1 2.2-2.2v-8.5H8Z"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.2"
|
||||
stroke="currentColor"
|
||||
stroke-width="1"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:pen-add"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 20, strokeWidth: 1.75 },
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M4 6h16M7 12h10M10 18h4"
|
||||
stroke="currentColor"
|
||||
:stroke-width="strokeWidth"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:filter"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 24 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="size" :height="size" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M4 14v-2a8 8 0 0 1 16 0v2"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<rect x="2" y="14" width="4" height="6" rx="2" stroke="currentColor" stroke-width="1.75" />
|
||||
<rect x="18" y="14" width="4" height="6" rx="2" stroke="currentColor" stroke-width="1.75" />
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:headphone"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.75 },
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M3 10.5L12 4l9 6.5V20a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1v-9.5Z"
|
||||
stroke="currentColor"
|
||||
:stroke-width="strokeWidth"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:home"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,28 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 22, strokeWidth: 1.75 },
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M6 3h12M6 21h12M8 3v4.5a4 4 0 0 0 2.5 3.7V14a4 4 0 0 0-2.5 3.7V21M16 3v4.5a4 4 0 0 1-2.5 3.7V14a4 4 0 0 1 2.5 3.7V21"
|
||||
stroke="currentColor"
|
||||
:stroke-width="strokeWidth"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:timer-1"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 24 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="size" :height="size" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<rect x="3" y="5" width="18" height="14" rx="2" stroke="currentColor" stroke-width="1.75" />
|
||||
<path d="M8 9V7a4 4 0 1 1 8 0v2" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" />
|
||||
<path d="M7 12h10" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" />
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:personalcard"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 20, strokeWidth: 1.75 },
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect x="4" y="4" width="7" height="7" rx="1.5" stroke="currentColor" :stroke-width="strokeWidth" />
|
||||
<rect x="13" y="4" width="7" height="7" rx="1.5" stroke="currentColor" :stroke-width="strokeWidth" />
|
||||
<rect x="4" y="13" width="7" height="7" rx="1.5" stroke="currentColor" :stroke-width="strokeWidth" />
|
||||
<rect x="13" y="13" width="7" height="7" rx="1.5" stroke="currentColor" :stroke-width="strokeWidth" />
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:element-3"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 24 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="size" :height="size" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M9 18h6"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M10 22h4"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M12 2a5 5 0 0 0-2.4 9.3V14h4.8v-2.7A5 5 0 0 0 12 2Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:lamp-on"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 24 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="size" :height="size" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<rect x="5" y="11" width="14" height="10" rx="2" stroke="currentColor" stroke-width="1.75" />
|
||||
<path
|
||||
d="M8 11V8a4 4 0 1 1 8 0v3"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:lock"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 24 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="size" :height="size" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<rect x="3" y="6" width="18" height="12" rx="2" stroke="currentColor" stroke-width="1.75" />
|
||||
<path d="M3 8l9 6 9-6" stroke="currentColor" stroke-width="1.75" stroke-linejoin="round" />
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:sms"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 24 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="size" :height="size" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M12 21s6-5.2 6-10a6 6 0 1 0-12 0c0 4.8 6 10 6 10Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<circle cx="12" cy="11" r="2" stroke="currentColor" stroke-width="1.75" />
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:location"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 20 },
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="12" cy="5" r="1.5" fill="currentColor" />
|
||||
<circle cx="12" cy="12" r="1.5" fill="currentColor" />
|
||||
<circle cx="12" cy="19" r="1.5" fill="currentColor" />
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:more"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,41 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.75 },
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect
|
||||
x="5"
|
||||
y="3"
|
||||
width="14"
|
||||
height="18"
|
||||
rx="2"
|
||||
stroke="currentColor"
|
||||
:stroke-width="strokeWidth"
|
||||
/>
|
||||
<path
|
||||
d="M9 7h6M9 11h6M9 15h4"
|
||||
stroke="currentColor"
|
||||
:stroke-width="strokeWidth"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M8 3V2a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v1"
|
||||
stroke="currentColor"
|
||||
:stroke-width="strokeWidth"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:document-text"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -4,6 +4,7 @@ withDefaults(defineProps<{ size?: number }>(), { size: 72 })
|
||||
|
||||
<template>
|
||||
<svg
|
||||
class="icon-logo"
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 72 72"
|
||||
@@ -13,14 +14,21 @@ withDefaults(defineProps<{ size?: number }>(), { size: 72 })
|
||||
>
|
||||
<text
|
||||
x="36"
|
||||
y="58"
|
||||
y="50"
|
||||
text-anchor="middle"
|
||||
dominant-baseline="middle"
|
||||
fill="currentColor"
|
||||
font-family="IRANSans, Tahoma, sans-serif"
|
||||
font-size="56"
|
||||
font-weight="700"
|
||||
style="font-family: IRANSans, Tahoma, sans-serif; font-size: 48px; font-weight: 700"
|
||||
>
|
||||
پ
|
||||
</text>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.icon-logo {
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 24 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="size" :height="size" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M8.5 4h7l1 4.5-3 1.5a12 12 0 0 0 4.5 4.5l1.5-3L21 15v7h-2.5A14.5 14.5 0 0 1 3 6.5V4"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:call"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,28 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.75 },
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="12" cy="8" r="4" stroke="currentColor" :stroke-width="strokeWidth" />
|
||||
<path
|
||||
d="M5 21v-1a5 5 0 0 1 5-5h4a5 5 0 0 1 5 5v1"
|
||||
stroke="currentColor"
|
||||
:stroke-width="strokeWidth"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:profile-circle"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,29 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 22, strokeWidth: 1.75 },
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="12" cy="12" r="9" stroke="currentColor" :stroke-width="strokeWidth" />
|
||||
<path
|
||||
d="M12 7v5l3 2"
|
||||
stroke="currentColor"
|
||||
:stroke-width="strokeWidth"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:clock"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 20 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="11" cy="11" r="7" />
|
||||
<path d="M20 20l-3.5-3.5" />
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:search-normal"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 24 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="size" :height="size" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="1.75" />
|
||||
<path
|
||||
d="M12 3v2M12 19v2M3 12h2M19 12h2M5.6 5.6l1.4 1.4M17 17l1.4 1.4M18.4 5.6 17 7M7 17l-1.4 1.4"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:setting-2"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 24 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="size" :height="size" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M8 8V6a4 4 0 1 1 8 0v2"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M6 8h12l-1 12H7L6 8Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:shopping-bag"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 48 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="size" :height="size" viewBox="0 0 48 48" fill="none" aria-hidden="true">
|
||||
<circle cx="24" cy="24" r="22" fill="#d4edda" />
|
||||
<circle cx="24" cy="24" r="14" fill="#28a745" />
|
||||
<path
|
||||
d="M17 24l4 4 8-8"
|
||||
stroke="#fff"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:tick-circle"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,48 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 20, strokeWidth: 1.5 },
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect
|
||||
x="10"
|
||||
y="4"
|
||||
width="4"
|
||||
height="2"
|
||||
rx="0.5"
|
||||
stroke="currentColor"
|
||||
:stroke-width="strokeWidth"
|
||||
/>
|
||||
<path
|
||||
d="M6.5 8h11"
|
||||
stroke="currentColor"
|
||||
:stroke-width="strokeWidth"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M8.5 8.5h7v10.75c0 .69-.56 1.25-1.25 1.25h-4.5c-.69 0-1.25-.56-1.25-1.25V8.5Z"
|
||||
stroke="currentColor"
|
||||
:stroke-width="strokeWidth"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M10.75 11.5v6.25M13.25 11.5v6.25"
|
||||
stroke="currentColor"
|
||||
:stroke-width="strokeWidth"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:trash"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 24 })
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
color?: string | null
|
||||
}>(),
|
||||
{ size: 24, strokeWidth: 1.5, color: null },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="size" :height="size" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="9" fill="currentColor" />
|
||||
<path
|
||||
d="M8 12.2 10.8 15 16 9.8"
|
||||
stroke="#fff"
|
||||
stroke-width="1.75"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<BasicIcon
|
||||
icon="vuesax-linear:verify"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -10,7 +10,9 @@ const cartStore = useCartStore()
|
||||
|
||||
const cartCount = computed(() => cartStore.itemCount)
|
||||
|
||||
function navigate(path: string) {
|
||||
function navigate(path: string, event: Event) {
|
||||
const target = event.currentTarget
|
||||
if (target instanceof HTMLElement) target.blur()
|
||||
router.push(path)
|
||||
}
|
||||
</script>
|
||||
@@ -24,13 +26,13 @@ function navigate(path: string) {
|
||||
class="nav-item"
|
||||
:class="{ active: isNavItemActive(item.name, item.path, route.path) }"
|
||||
:aria-current="isNavItemActive(item.name, item.path, route.path) ? 'page' : undefined"
|
||||
@click="navigate(item.path)"
|
||||
@click="navigate(item.path, $event)"
|
||||
>
|
||||
<span class="nav-icon">
|
||||
<component :is="item.icon" :size="22" />
|
||||
<component :is="item.icon" :size="22" :stroke-width="1.5" />
|
||||
<span v-if="item.name === 'cart' && cartCount > 0" class="nav-badge">{{ cartCount }}</span>
|
||||
</span>
|
||||
<span class="nav-label">{{ item.label }}</span>
|
||||
<span v-if="item.name === 'cart' && cartCount > 0" class="nav-badge">{{ cartCount }}</span>
|
||||
</button>
|
||||
</nav>
|
||||
</template>
|
||||
@@ -38,14 +40,14 @@ function navigate(path: string) {
|
||||
<style scoped>
|
||||
.bottom-nav {
|
||||
flex-shrink: 0;
|
||||
height: var(--nav-height);
|
||||
min-height: var(--nav-height);
|
||||
background: var(--color-surface);
|
||||
border-top: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: stretch;
|
||||
z-index: 100;
|
||||
padding-bottom: env(safe-area-inset-bottom, 0);
|
||||
padding: 6px 4px calc(6px + env(safe-area-inset-bottom, 0));
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
@@ -61,38 +63,54 @@ function navigate(path: string) {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
padding: 8px 4px;
|
||||
margin: 0;
|
||||
padding: 4px 2px;
|
||||
border: none;
|
||||
background: none;
|
||||
border-radius: 10px;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
color: var(--color-text-muted);
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.nav-item:focus,
|
||||
.nav-item:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
color: var(--color-primary);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.nav-item.active .nav-label {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
flex-shrink: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.nav-label {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nav-badge {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
inset-inline-start: 50%;
|
||||
margin-inline-start: 10px;
|
||||
top: -4px;
|
||||
inset-inline-end: -6px;
|
||||
background: var(--color-danger);
|
||||
color: #fff;
|
||||
font-size: 0.625rem;
|
||||
@@ -104,5 +122,7 @@ function navigate(path: string) {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 4px;
|
||||
line-height: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -121,8 +121,8 @@ defineEmits<{
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
border-color: #cbd5e1;
|
||||
background: #f8fafc;
|
||||
border-color: var(--color-border);
|
||||
background: var(--color-summary-bg);
|
||||
}
|
||||
|
||||
.header-text {
|
||||
|
||||
@@ -39,7 +39,7 @@ defineProps<{
|
||||
}
|
||||
|
||||
.status-hero--waiting .status-hero__icon {
|
||||
background: #eef4ff;
|
||||
background: var(--color-primary-soft);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ function settlementLabel(type: 'cash' | 'check') {
|
||||
.tracking-summary {
|
||||
padding: 16px;
|
||||
border-radius: 16px;
|
||||
background: #ffffff;
|
||||
background: var(--color-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
@@ -67,7 +67,7 @@ function settlementLabel(type: 'cash' | 'check') {
|
||||
gap: 12px;
|
||||
padding: 10px 0;
|
||||
font-size: 0.875rem;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.tracking-summary__row:last-child {
|
||||
|
||||
@@ -80,7 +80,8 @@ function timestampFor(step: OrderStatus) {
|
||||
margin: 4px 0;
|
||||
padding: 12px;
|
||||
border-radius: 14px;
|
||||
background: #eef4ff;
|
||||
background: var(--color-primary-soft);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.tracking-timeline__marker-wrap {
|
||||
@@ -94,7 +95,7 @@ function timestampFor(step: OrderStatus) {
|
||||
top: 22px;
|
||||
bottom: -10px;
|
||||
width: 2px;
|
||||
background: #e2e8f0;
|
||||
background: var(--color-border);
|
||||
}
|
||||
|
||||
.tracking-timeline__item:last-child .tracking-timeline__line {
|
||||
@@ -109,8 +110,8 @@ function timestampFor(step: OrderStatus) {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
background: #e2e8f0;
|
||||
border: 2px solid #e2e8f0;
|
||||
background: var(--color-border);
|
||||
border: 2px solid var(--color-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -126,12 +127,12 @@ function timestampFor(step: OrderStatus) {
|
||||
.tracking-timeline__item--current .tracking-timeline__marker {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 4px rgba(29, 53, 87, 0.12);
|
||||
box-shadow: 0 0 0 4px var(--color-primary-soft);
|
||||
}
|
||||
|
||||
.tracking-timeline__item--pending .tracking-timeline__marker {
|
||||
background: #fff;
|
||||
border-color: #cbd5e1;
|
||||
background: var(--color-elevated);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.tracking-timeline__check {
|
||||
|
||||
@@ -46,7 +46,7 @@ defineProps<{
|
||||
}
|
||||
|
||||
.badge-neutral {
|
||||
background: #f1f5f9;
|
||||
background: var(--color-summary-bg);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
modelValue?: string | number
|
||||
label?: string
|
||||
type?: string
|
||||
placeholder?: string
|
||||
hint?: string
|
||||
error?: string
|
||||
disabled?: boolean
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
}>()
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: string | number
|
||||
label?: string
|
||||
type?: string
|
||||
placeholder?: string
|
||||
hint?: string
|
||||
error?: string
|
||||
disabled?: boolean
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
maxlength?: number | string
|
||||
inputmode?: 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url'
|
||||
autocomplete?: string
|
||||
dir?: string
|
||||
align?: 'start' | 'center' | 'end'
|
||||
}>(),
|
||||
{
|
||||
align: 'start',
|
||||
},
|
||||
)
|
||||
|
||||
defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
blur: [event: FocusEvent]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -22,7 +33,7 @@ defineEmits<{
|
||||
<label v-if="label" class="form-label">{{ label }}</label>
|
||||
<input
|
||||
class="input"
|
||||
:class="{ 'input-error': error }"
|
||||
:class="[`input--align-${align}`, { 'input-error': error }]"
|
||||
:type="type ?? 'text'"
|
||||
:value="modelValue"
|
||||
:placeholder="placeholder"
|
||||
@@ -30,7 +41,12 @@ defineEmits<{
|
||||
:min="min"
|
||||
:max="max"
|
||||
:step="step"
|
||||
:maxlength="maxlength"
|
||||
:inputmode="inputmode"
|
||||
:autocomplete="autocomplete"
|
||||
:dir="dir"
|
||||
@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
|
||||
@blur="$emit('blur', $event)"
|
||||
/>
|
||||
<p v-if="error" class="input-error-text">{{ error }}</p>
|
||||
<p v-else-if="hint" class="form-hint">{{ hint }}</p>
|
||||
@@ -50,6 +66,19 @@ defineEmits<{
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
|
||||
.input--align-start {
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.input--align-center {
|
||||
text-align: center;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.input--align-end {
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
|
||||
@@ -1,20 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import IconClipboardEmpty from '@/components/icons/IconClipboardEmpty.vue'
|
||||
import { computed } from 'vue'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
defineProps<{
|
||||
message?: string
|
||||
}>()
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
message?: string
|
||||
title?: string
|
||||
/** Context for default icon + copy */
|
||||
variant?: 'default' | 'products' | 'catalogs' | 'orders' | 'cart' | 'checks' | 'search'
|
||||
icon?: string
|
||||
iconSize?: number
|
||||
}>(),
|
||||
{
|
||||
message: undefined,
|
||||
title: undefined,
|
||||
variant: 'default',
|
||||
icon: undefined,
|
||||
iconSize: 72,
|
||||
},
|
||||
)
|
||||
|
||||
const variantIcons: Record<string, string> = {
|
||||
default: 'vuesax-linear:clipboard-close',
|
||||
products: 'vuesax-linear:box-remove',
|
||||
catalogs: 'vuesax-linear:category',
|
||||
orders: 'vuesax-linear:document-text',
|
||||
cart: 'vuesax-linear:bag-cross',
|
||||
checks: 'vuesax-linear:card-receive',
|
||||
search: 'vuesax-linear:search-status',
|
||||
}
|
||||
|
||||
const variantTitles: Record<string, string> = {
|
||||
default: '',
|
||||
products: 'محصولی یافت نشد',
|
||||
catalogs: 'کالیتهای یافت نشد',
|
||||
orders: 'سفارشی یافت نشد',
|
||||
cart: 'سبد خرید خالی است',
|
||||
checks: 'چکی ثبت نشده است',
|
||||
search: 'نتیجهای یافت نشد',
|
||||
}
|
||||
|
||||
const resolvedIcon = computed(
|
||||
() => props.icon ?? variantIcons[props.variant] ?? 'vuesax-linear:clipboard-close',
|
||||
)
|
||||
const resolvedTitle = computed(() => props.title ?? variantTitles[props.variant] ?? '')
|
||||
const resolvedMessage = computed(() => props.message ?? '')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="empty-state">
|
||||
<div class="empty-state__icon">
|
||||
<div class="empty-state__icon" aria-hidden="true">
|
||||
<slot name="icon">
|
||||
<IconClipboardEmpty :size="72" />
|
||||
<BasicIcon :icon="resolvedIcon" :size="iconSize" :stroke-width="1.25" />
|
||||
</slot>
|
||||
</div>
|
||||
<p class="empty-state__message">
|
||||
<slot name="message">{{ message }}</slot>
|
||||
<h2 v-if="resolvedTitle || $slots.title" class="empty-state__title">
|
||||
<slot name="title">{{ resolvedTitle }}</slot>
|
||||
</h2>
|
||||
<p v-if="resolvedMessage || $slots.message" class="empty-state__message">
|
||||
<slot name="message">{{ resolvedMessage }}</slot>
|
||||
</p>
|
||||
<div v-if="$slots.default" class="empty-state__action">
|
||||
<slot />
|
||||
@@ -29,20 +73,36 @@ defineProps<{
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 48px 24px;
|
||||
min-height: 320px;
|
||||
min-height: 280px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-state__icon {
|
||||
margin-bottom: 16px;
|
||||
color: var(--color-text-muted);
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.empty-state__title {
|
||||
margin: 0 0 8px;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.empty-state__message {
|
||||
margin: 0 0 24px;
|
||||
font-size: 0.9375rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-muted);
|
||||
line-height: 1.5;
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.empty-state__message:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.empty-state__action {
|
||||
|
||||
@@ -50,10 +50,10 @@ defineEmits<{
|
||||
flex-shrink: 0;
|
||||
height: 36px;
|
||||
padding: 0 18px;
|
||||
border: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 999px;
|
||||
background: #f5f5f5;
|
||||
color: #64748b;
|
||||
background: var(--color-tab-inactive-bg);
|
||||
color: var(--color-tab-inactive-text);
|
||||
font-family: inherit;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
@@ -61,7 +61,8 @@ defineEmits<{
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background 0.15s,
|
||||
color 0.15s;
|
||||
color 0.15s,
|
||||
border-color 0.15s;
|
||||
}
|
||||
|
||||
.filter-tabs--equal .filter-tab {
|
||||
@@ -76,6 +77,7 @@ defineEmits<{
|
||||
|
||||
.filter-tab.active {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -85,6 +87,7 @@ defineEmits<{
|
||||
}
|
||||
|
||||
.filter-tab:not(.active):hover {
|
||||
background: #eceff1;
|
||||
border-color: var(--color-border);
|
||||
color: var(--color-text);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -269,7 +269,7 @@ onBeforeUnmount(() => {
|
||||
height: 32px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: #fff;
|
||||
background: var(--color-elevated);
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
font-size: 1.125rem;
|
||||
|
||||
@@ -37,7 +37,7 @@ defineEmits<{
|
||||
}
|
||||
|
||||
.search-field__input::placeholder {
|
||||
color: #94a3b8;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.search-field__input:focus {
|
||||
|
||||
@@ -41,8 +41,8 @@ export function useCartPage() {
|
||||
router.push('/checkout/settlement')
|
||||
}
|
||||
|
||||
function createNewInvoice() {
|
||||
cartStore.createNewCartForSameCompany()
|
||||
async function createNewInvoice() {
|
||||
await cartStore.createNewCartForSameCompany()
|
||||
showFullDialog.value = false
|
||||
const companyId = cartStore.selectedCompanyId || cartStore.activeCart?.companyId
|
||||
if (companyId) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { computed, ref, watch, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useOrderStore } from '@/stores/order'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { getCompanyById } from '@/data/mockData'
|
||||
import { useCatalogStore } from '@/stores/catalog'
|
||||
import type { Order } from '@/types'
|
||||
import {
|
||||
DELIVERED_STATUSES,
|
||||
@@ -14,12 +14,19 @@ import {
|
||||
} from '@/utils/orderStatusGroups'
|
||||
|
||||
const DESKTOP_PAGE_SIZE = 5
|
||||
const MOBILE_PAGE_SIZE = 5
|
||||
|
||||
export function useOrderList() {
|
||||
const router = useRouter()
|
||||
const orderStore = useOrderStore()
|
||||
const authStore = useAuthStore()
|
||||
const catalogStore = useCatalogStore()
|
||||
const currentPage = ref(1)
|
||||
const mobilePage = ref(1)
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([orderStore.hydrate(), catalogStore.loadCompanies()])
|
||||
})
|
||||
|
||||
const tab = computed({
|
||||
get: () => (router.currentRoute.value.query.tab as string) ?? 'all',
|
||||
@@ -103,12 +110,50 @@ export function useOrderList() {
|
||||
return pages
|
||||
})
|
||||
|
||||
const mobileTotalPages = computed(() =>
|
||||
Math.max(1, Math.ceil(filteredOrdersMobile.value.length / MOBILE_PAGE_SIZE)),
|
||||
)
|
||||
|
||||
const paginatedOrdersMobile = computed(() => {
|
||||
const start = (mobilePage.value - 1) * MOBILE_PAGE_SIZE
|
||||
return filteredOrdersMobile.value.slice(start, start + MOBILE_PAGE_SIZE)
|
||||
})
|
||||
|
||||
const mobilePaginationRange = computed(() => {
|
||||
if (filteredOrdersMobile.value.length === 0) return { start: 0, end: 0 }
|
||||
const start = (mobilePage.value - 1) * MOBILE_PAGE_SIZE + 1
|
||||
const end = Math.min(mobilePage.value * MOBILE_PAGE_SIZE, filteredOrdersMobile.value.length)
|
||||
return { start, end }
|
||||
})
|
||||
|
||||
const mobileVisiblePages = computed(() => {
|
||||
const total = mobileTotalPages.value
|
||||
const current = mobilePage.value
|
||||
if (total <= 5) return Array.from({ length: total }, (_, i) => i + 1)
|
||||
|
||||
const pages: (number | 'ellipsis')[] = [1]
|
||||
if (current > 3) pages.push('ellipsis')
|
||||
|
||||
const rangeStart = Math.max(2, current - 1)
|
||||
const rangeEnd = Math.min(total - 1, current + 1)
|
||||
for (let p = rangeStart; p <= rangeEnd; p += 1) pages.push(p)
|
||||
|
||||
if (current < total - 2) pages.push('ellipsis')
|
||||
if (total > 1) pages.push(total)
|
||||
|
||||
return pages
|
||||
})
|
||||
|
||||
watch([filteredOrdersDesktop, desktopFilter, searchQuery], () => {
|
||||
currentPage.value = 1
|
||||
})
|
||||
|
||||
watch([filteredOrdersMobile, tab, searchQuery], () => {
|
||||
mobilePage.value = 1
|
||||
})
|
||||
|
||||
function companySlug(order: Order) {
|
||||
return getCompanyById(order.companyId)?.slug ?? 'par'
|
||||
return catalogStore.getCompany(order.companyId)?.slug ?? 'par'
|
||||
}
|
||||
|
||||
function iconVariant(order: Order) {
|
||||
@@ -125,6 +170,11 @@ export function useOrderList() {
|
||||
currentPage.value = page
|
||||
}
|
||||
|
||||
function setMobilePage(page: number) {
|
||||
if (page < 1 || page > mobileTotalPages.value) return
|
||||
mobilePage.value = page
|
||||
}
|
||||
|
||||
function setDesktopFilter(filter: DesktopOrderFilterId) {
|
||||
desktopFilter.value = desktopFilter.value === filter && filter !== 'all' ? 'all' : filter
|
||||
}
|
||||
@@ -141,14 +191,20 @@ export function useOrderList() {
|
||||
filteredOrdersMobile,
|
||||
filteredOrdersDesktop,
|
||||
paginatedOrders,
|
||||
paginatedOrdersMobile,
|
||||
totalPages,
|
||||
mobileTotalPages,
|
||||
paginationRange,
|
||||
mobilePaginationRange,
|
||||
visiblePages,
|
||||
mobileVisiblePages,
|
||||
currentPage,
|
||||
mobilePage,
|
||||
companySlug,
|
||||
iconVariant,
|
||||
goToOrder,
|
||||
setPage,
|
||||
setMobilePage,
|
||||
setDesktopFilter,
|
||||
viewStatFilter,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useCatalogStore } from '@/stores/catalog'
|
||||
import { useCartStore } from '@/stores/cart'
|
||||
@@ -8,6 +8,7 @@ export function useProductOrdering() {
|
||||
const router = useRouter()
|
||||
const catalogStore = useCatalogStore()
|
||||
const cartStore = useCartStore()
|
||||
const loading = ref(true)
|
||||
|
||||
const companyId = computed(() => route.params.companyId as string)
|
||||
const catalogId = computed(() => route.params.catalogId as string)
|
||||
@@ -29,6 +30,27 @@ export function useProductOrdering() {
|
||||
const errors = ref<Record<string, string>>({})
|
||||
const toast = ref('')
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
if (!catalogStore.getCompany(companyId.value)) {
|
||||
await catalogStore.loadCompanies()
|
||||
}
|
||||
if (!catalogStore.getCatalog(catalogId.value)) {
|
||||
await catalogStore.loadCatalogs(companyId.value)
|
||||
}
|
||||
await catalogStore.loadProducts(catalogId.value)
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
watch([companyId, catalogId], load)
|
||||
watch(colorFilter, async (color) => {
|
||||
await catalogStore.loadProducts(
|
||||
catalogId.value,
|
||||
color === 'all' ? undefined : color,
|
||||
)
|
||||
})
|
||||
|
||||
function getMeterage(productId: string) {
|
||||
return meterages.value[productId] ?? 0
|
||||
}
|
||||
@@ -38,7 +60,7 @@ export function useProductOrdering() {
|
||||
meterages.value[productId] = Math.max(0, current + delta * step)
|
||||
}
|
||||
|
||||
function addProduct(productId: string) {
|
||||
async function addProduct(productId: string) {
|
||||
const product = catalogStore.getProduct(productId)
|
||||
if (!product || !catalog.value) return
|
||||
|
||||
@@ -48,7 +70,7 @@ export function useProductOrdering() {
|
||||
return
|
||||
}
|
||||
|
||||
const result = cartStore.addItem(product, catalog.value.code, meterage)
|
||||
const result = await cartStore.addItem(product, catalog.value.code, meterage)
|
||||
if (result.success) {
|
||||
errors.value[productId] = ''
|
||||
toast.value = `${product.name} به سبد اضافه شد.`
|
||||
@@ -84,6 +106,7 @@ export function useProductOrdering() {
|
||||
meterages,
|
||||
errors,
|
||||
toast,
|
||||
loading,
|
||||
cartStore,
|
||||
adjustMeterage,
|
||||
setMeterage,
|
||||
|
||||
+6
-1
@@ -3,6 +3,8 @@ import { createPinia } from 'pinia'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
import 'virtual:uno.css'
|
||||
import './assets/styles/fonts.css'
|
||||
import './assets/styles/main.css'
|
||||
import './assets/styles/app-shell.css'
|
||||
@@ -11,8 +13,11 @@ import './assets/styles/desktop.css'
|
||||
import './assets/styles/desktop-pages.css'
|
||||
|
||||
const app = createApp(App)
|
||||
const pinia = createPinia()
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(pinia)
|
||||
app.use(router)
|
||||
|
||||
useThemeStore(pinia)
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { getCurrentUser } from '@/services/authService'
|
||||
import { isAuthenticated } from '@/services/authService'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
@@ -137,13 +137,13 @@ const router = createRouter({
|
||||
})
|
||||
|
||||
router.beforeEach((to) => {
|
||||
const isAuthenticated = !!getCurrentUser()
|
||||
const authed = isAuthenticated()
|
||||
|
||||
if (to.meta.requiresAuth && !isAuthenticated) {
|
||||
if (to.meta.requiresAuth && !authed) {
|
||||
return { name: 'login', query: { redirect: to.fullPath } }
|
||||
}
|
||||
|
||||
if (to.meta.guest && isAuthenticated) {
|
||||
if (to.meta.guest && authed) {
|
||||
return { name: 'home' }
|
||||
}
|
||||
|
||||
|
||||
+86
-93
@@ -1,37 +1,31 @@
|
||||
import { MOCK_OTP_CODE } from '@/utils/constants'
|
||||
import { generateId, normalizePhone } from '@/utils/format'
|
||||
import { getItem, setItem, STORAGE_KEYS } from './storage'
|
||||
import { normalizePhone } from '@/utils/format'
|
||||
import { apiRequest, ApiError, getAccessToken, setAccessToken } from './http'
|
||||
import type { User } from '@/types'
|
||||
|
||||
interface PendingOtp {
|
||||
phone: string
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
isRegister: boolean
|
||||
code: string
|
||||
expiresAt: string
|
||||
const USER_KEY = 'ft_user'
|
||||
|
||||
function cacheUser(user: User | null): void {
|
||||
if (user) localStorage.setItem(USER_KEY, JSON.stringify(user))
|
||||
else localStorage.removeItem(USER_KEY)
|
||||
}
|
||||
|
||||
export function getUsers(): User[] {
|
||||
return getItem<User[]>(STORAGE_KEYS.users, [])
|
||||
}
|
||||
|
||||
function saveUsers(users: User[]): void {
|
||||
setItem(STORAGE_KEYS.users, users)
|
||||
}
|
||||
|
||||
export function getSessionUserId(): string | null {
|
||||
return getItem<string | null>(STORAGE_KEYS.session, null)
|
||||
}
|
||||
|
||||
export function setSessionUserId(userId: string | null): void {
|
||||
setItem(STORAGE_KEYS.session, userId)
|
||||
export function getCachedUser(): User | null {
|
||||
try {
|
||||
const raw = localStorage.getItem(USER_KEY)
|
||||
if (!raw) return null
|
||||
return JSON.parse(raw) as User
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function getCurrentUser(): User | null {
|
||||
const id = getSessionUserId()
|
||||
if (!id) return null
|
||||
return getUsers().find((u) => u.id === id) ?? null
|
||||
if (!getAccessToken()) return null
|
||||
return getCachedUser()
|
||||
}
|
||||
|
||||
export function isAuthenticated(): boolean {
|
||||
return !!getAccessToken()
|
||||
}
|
||||
|
||||
export async function sendOtp(
|
||||
@@ -43,34 +37,31 @@ export async function sendOtp(
|
||||
return { success: false, message: 'شماره موبایل معتبر نیست.' }
|
||||
}
|
||||
|
||||
if (options.isRegister) {
|
||||
if (!options.firstName?.trim() || !options.lastName?.trim()) {
|
||||
return { success: false, message: 'نام و نام خانوادگی الزامی است.' }
|
||||
}
|
||||
const exists = getUsers().some((u) => u.phone === normalized)
|
||||
if (exists) {
|
||||
return { success: false, message: 'این شماره قبلاً ثبت شده است. وارد شوید.' }
|
||||
}
|
||||
} else {
|
||||
const exists = getUsers().some((u) => u.phone === normalized)
|
||||
if (!exists) {
|
||||
return { success: false, message: 'کاربری با این شماره یافت نشد. ثبتنام کنید.' }
|
||||
}
|
||||
if (options.isRegister && (!options.firstName?.trim() || !options.lastName?.trim())) {
|
||||
return { success: false, message: 'نام و نام خانوادگی الزامی است.' }
|
||||
}
|
||||
|
||||
const pending: PendingOtp = {
|
||||
phone: normalized,
|
||||
firstName: options.firstName,
|
||||
lastName: options.lastName,
|
||||
isRegister: options.isRegister,
|
||||
code: MOCK_OTP_CODE,
|
||||
expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
|
||||
}
|
||||
setItem(STORAGE_KEYS.pendingOtp, pending)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `کد تأیید ارسال شد. (نسخه آزمایشی: ${MOCK_OTP_CODE})`,
|
||||
try {
|
||||
const res = await apiRequest<{ success: boolean; message: string; debugCode?: string }>(
|
||||
'/auth/otp/request',
|
||||
{
|
||||
method: 'POST',
|
||||
auth: false,
|
||||
body: {
|
||||
phone: normalized,
|
||||
firstName: options.firstName,
|
||||
lastName: options.lastName,
|
||||
isRegister: options.isRegister,
|
||||
},
|
||||
},
|
||||
)
|
||||
const hint = res.debugCode ? ` (کد آزمایشی: ${res.debugCode})` : ''
|
||||
return { success: true, message: `${res.message}${hint}` }
|
||||
} catch (e) {
|
||||
return {
|
||||
success: false,
|
||||
message: e instanceof ApiError ? e.message : 'ارسال کد ناموفق بود.',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,53 +69,55 @@ export async function verifyOtp(
|
||||
phone: string,
|
||||
code: string,
|
||||
): Promise<{ success: boolean; message: string; user?: User }> {
|
||||
const pending = getItem<PendingOtp | null>(STORAGE_KEYS.pendingOtp, null)
|
||||
const normalized = normalizePhone(phone)
|
||||
|
||||
if (!pending || pending.phone !== normalized) {
|
||||
return { success: false, message: 'ابتدا درخواست کد دهید.' }
|
||||
}
|
||||
if (new Date(pending.expiresAt) < new Date()) {
|
||||
return { success: false, message: 'کد منقضی شده است. دوباره درخواست دهید.' }
|
||||
}
|
||||
if (code.trim() !== pending.code) {
|
||||
return { success: false, message: 'کد تأیید نادرست است.' }
|
||||
}
|
||||
|
||||
let user: User
|
||||
const users = getUsers()
|
||||
|
||||
if (pending.isRegister) {
|
||||
user = {
|
||||
id: generateId(),
|
||||
firstName: pending.firstName!,
|
||||
lastName: pending.lastName!,
|
||||
phone: normalized,
|
||||
createdAt: new Date().toISOString(),
|
||||
try {
|
||||
const res = await apiRequest<{ accessToken: string; user: User }>('/auth/otp/verify', {
|
||||
method: 'POST',
|
||||
auth: false,
|
||||
body: { phone: normalized, code },
|
||||
})
|
||||
setAccessToken(res.accessToken)
|
||||
cacheUser(res.user)
|
||||
return { success: true, message: 'ورود موفق.', user: res.user }
|
||||
} catch (e) {
|
||||
return {
|
||||
success: false,
|
||||
message: e instanceof ApiError ? e.message : 'تأیید کد ناموفق بود.',
|
||||
}
|
||||
users.push(user)
|
||||
saveUsers(users)
|
||||
} else {
|
||||
user = users.find((u) => u.phone === normalized)!
|
||||
}
|
||||
|
||||
setSessionUserId(user.id)
|
||||
setItem(STORAGE_KEYS.pendingOtp, null)
|
||||
return { success: true, message: 'ورود موفق.', user }
|
||||
}
|
||||
|
||||
export function logout(): void {
|
||||
setSessionUserId(null)
|
||||
setAccessToken(null)
|
||||
cacheUser(null)
|
||||
}
|
||||
|
||||
export function updateProfile(
|
||||
userId: string,
|
||||
export async function fetchMe(): Promise<User | null> {
|
||||
if (!getAccessToken()) {
|
||||
cacheUser(null)
|
||||
return null
|
||||
}
|
||||
try {
|
||||
const user = await apiRequest<User>('/auth/me')
|
||||
cacheUser(user)
|
||||
return user
|
||||
} catch {
|
||||
logout()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateProfile(
|
||||
data: Partial<Pick<User, 'firstName' | 'lastName' | 'nationalId'>>,
|
||||
): User | null {
|
||||
const users = getUsers()
|
||||
const index = users.findIndex((u) => u.id === userId)
|
||||
if (index === -1) return null
|
||||
users[index] = { ...users[index]!, ...data }
|
||||
saveUsers(users)
|
||||
return users[index]!
|
||||
): Promise<User | null> {
|
||||
try {
|
||||
const user = await apiRequest<User>('/auth/profile', {
|
||||
method: 'PATCH',
|
||||
body: data,
|
||||
})
|
||||
cacheUser(user)
|
||||
return user
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
const TOKEN_KEY = 'ft_access_token'
|
||||
|
||||
export function getApiBaseUrl(): string {
|
||||
return (import.meta.env.VITE_API_BASE_URL as string | undefined)?.replace(/\/$/, '')
|
||||
|| 'https://parche.montech.ir/server/api'
|
||||
}
|
||||
|
||||
export function getAccessToken(): string | null {
|
||||
return localStorage.getItem(TOKEN_KEY)
|
||||
}
|
||||
|
||||
export function setAccessToken(token: string | null): void {
|
||||
if (token) localStorage.setItem(TOKEN_KEY, token)
|
||||
else localStorage.removeItem(TOKEN_KEY)
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
status: number
|
||||
constructor(status: number, message: string) {
|
||||
super(message)
|
||||
this.status = status
|
||||
}
|
||||
}
|
||||
|
||||
type RequestOptions = {
|
||||
method?: string
|
||||
body?: unknown
|
||||
auth?: boolean
|
||||
query?: Record<string, string | number | boolean | undefined | null>
|
||||
}
|
||||
|
||||
function buildUrl(path: string, query?: RequestOptions['query']): string {
|
||||
const base = getApiBaseUrl()
|
||||
const url = new URL(path.startsWith('http') ? path : `${base}${path.startsWith('/') ? '' : '/'}${path}`)
|
||||
if (query) {
|
||||
for (const [key, value] of Object.entries(query)) {
|
||||
if (value === undefined || value === null || value === '') continue
|
||||
url.searchParams.set(key, String(value))
|
||||
}
|
||||
}
|
||||
return url.toString()
|
||||
}
|
||||
|
||||
export async function apiRequest<T>(path: string, options: RequestOptions = {}): Promise<T> {
|
||||
const headers: Record<string, string> = {
|
||||
Accept: 'application/json',
|
||||
}
|
||||
if (options.body !== undefined) {
|
||||
headers['Content-Type'] = 'application/json'
|
||||
}
|
||||
if (options.auth !== false) {
|
||||
const token = getAccessToken()
|
||||
if (token) headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
|
||||
const res = await fetch(buildUrl(path, options.query), {
|
||||
method: options.method ?? (options.body !== undefined ? 'POST' : 'GET'),
|
||||
headers,
|
||||
body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
|
||||
})
|
||||
|
||||
const text = await res.text()
|
||||
let data: unknown = null
|
||||
if (text) {
|
||||
try {
|
||||
data = JSON.parse(text)
|
||||
} catch {
|
||||
data = text
|
||||
}
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
const message =
|
||||
typeof data === 'object' && data && 'message' in data
|
||||
? Array.isArray((data as { message: unknown }).message)
|
||||
? ((data as { message: string[] }).message).join('، ')
|
||||
: String((data as { message: unknown }).message)
|
||||
: `خطای سرور (${res.status})`
|
||||
throw new ApiError(res.status, message)
|
||||
}
|
||||
|
||||
return data as T
|
||||
}
|
||||
+17
-3
@@ -7,7 +7,11 @@ import {
|
||||
sendOtp,
|
||||
verifyOtp,
|
||||
updateProfile as authUpdateProfile,
|
||||
fetchMe,
|
||||
} from '@/services/authService'
|
||||
import { useCartStore } from './cart'
|
||||
import { useOrderStore } from './order'
|
||||
import { useCatalogStore } from './catalog'
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const user = ref<User | null>(getCurrentUser())
|
||||
@@ -42,6 +46,11 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
loading.value = false
|
||||
if (result.success && result.user) {
|
||||
user.value = result.user
|
||||
await Promise.all([
|
||||
useCatalogStore().loadCompanies(),
|
||||
useCartStore().hydrate(),
|
||||
useOrderStore().hydrate(),
|
||||
])
|
||||
} else {
|
||||
error.value = result.message
|
||||
}
|
||||
@@ -51,17 +60,22 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
function logout() {
|
||||
authLogout()
|
||||
user.value = null
|
||||
useCartStore().resetLocal()
|
||||
useOrderStore().resetLocal()
|
||||
}
|
||||
|
||||
function updateProfile(data: Partial<Pick<User, 'firstName' | 'lastName' | 'nationalId'>>) {
|
||||
async function updateProfile(data: Partial<Pick<User, 'firstName' | 'lastName' | 'nationalId'>>) {
|
||||
if (!user.value) return null
|
||||
const updated = authUpdateProfile(user.value.id, data)
|
||||
const updated = await authUpdateProfile(data)
|
||||
if (updated) user.value = updated
|
||||
return updated
|
||||
}
|
||||
|
||||
function hydrate() {
|
||||
async function hydrate() {
|
||||
user.value = getCurrentUser()
|
||||
if (!user.value) return
|
||||
const me = await fetchMe()
|
||||
user.value = me
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
+135
-166
@@ -1,31 +1,22 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { Cart, CartItem, Company, Product } from '@/types'
|
||||
import { generateId } from '@/utils/format'
|
||||
import type { Cart, Company, Product } from '@/types'
|
||||
import { validateMeterage } from '@/utils/validation'
|
||||
import { getItem, setItem, STORAGE_KEYS } from '@/services/storage'
|
||||
import { getCompanyById } from '@/data/mockData'
|
||||
import { apiRequest, ApiError } from '@/services/http'
|
||||
import { getAccessToken } from '@/services/http'
|
||||
import { useCatalogStore } from './catalog'
|
||||
|
||||
const DEFAULT_MAX_METERAGE = 5000
|
||||
|
||||
function loadCarts(): Cart[] {
|
||||
return getItem<Cart[]>(STORAGE_KEYS.carts, [])
|
||||
type ApiCart = Cart & { totalMeterage?: number; itemCount?: number }
|
||||
|
||||
function emptyCart(): Cart {
|
||||
return { id: 'local', companyId: '', companyName: '', items: [] }
|
||||
}
|
||||
|
||||
function saveCarts(carts: Cart[]) {
|
||||
setItem(STORAGE_KEYS.carts, carts)
|
||||
}
|
||||
|
||||
function loadActiveCartId(): string | null {
|
||||
return getItem<string | null>(STORAGE_KEYS.activeCartId, null)
|
||||
}
|
||||
|
||||
function saveActiveCartId(id: string | null) {
|
||||
setItem(STORAGE_KEYS.activeCartId, id)
|
||||
}
|
||||
|
||||
function getCartTotalMeterage(cart: Cart): number {
|
||||
return cart.items.reduce((sum, item) => sum + item.meterage, 0)
|
||||
function uniqueCompanyNames(items: Cart['items']): string {
|
||||
const names = [...new Set(items.map((item) => item.companyName).filter(Boolean))]
|
||||
return names.join('، ')
|
||||
}
|
||||
|
||||
function companyMeterage(cart: Cart, companyId: string): number {
|
||||
@@ -34,35 +25,13 @@ function companyMeterage(cart: Cart, companyId: string): number {
|
||||
.reduce((sum, item) => sum + item.meterage, 0)
|
||||
}
|
||||
|
||||
function uniqueCompanyNames(items: CartItem[]): string {
|
||||
const names = [...new Set(items.map((item) => item.companyName).filter(Boolean))]
|
||||
return names.join('، ')
|
||||
}
|
||||
|
||||
function syncCartCompanyMeta(cart: Cart) {
|
||||
if (cart.items.length === 0) return
|
||||
const first = cart.items[0]!
|
||||
cart.companyId = first.companyId
|
||||
cart.companyName = uniqueCompanyNames(cart.items) || first.companyName
|
||||
}
|
||||
|
||||
function migrateCartItems(carts: Cart[]): Cart[] {
|
||||
return carts.map((cart) => ({
|
||||
...cart,
|
||||
items: cart.items.map((item) => ({
|
||||
...item,
|
||||
companyId: item.companyId || cart.companyId,
|
||||
companyName: item.companyName || cart.companyName,
|
||||
})),
|
||||
}))
|
||||
}
|
||||
|
||||
export const useCartStore = defineStore('cart', () => {
|
||||
const carts = ref<Cart[]>(migrateCartItems(loadCarts()))
|
||||
const activeCartId = ref<string | null>(loadActiveCartId())
|
||||
const carts = ref<Cart[]>([emptyCart()])
|
||||
const activeCartId = ref<string | null>('local')
|
||||
const selectedCompanyId = ref<string | null>(null)
|
||||
const syncing = ref(false)
|
||||
|
||||
const activeCart = computed(() => carts.value.find((c) => c.id === activeCartId.value) ?? null)
|
||||
const activeCart = computed(() => carts.value.find((c) => c.id === activeCartId.value) ?? carts.value[0] ?? null)
|
||||
|
||||
const totalMeterage = computed(
|
||||
() => activeCart.value?.items.reduce((sum, i) => sum + i.meterage, 0) ?? 0,
|
||||
@@ -75,107 +44,101 @@ export const useCartStore = defineStore('cart', () => {
|
||||
return uniqueCompanyNames(activeCart.value.items)
|
||||
})
|
||||
|
||||
const catalogStore = useCatalogStore()
|
||||
|
||||
const maxMeterage = computed(() => {
|
||||
if (!activeCart.value?.items.length) {
|
||||
const selected = selectedCompanyId.value
|
||||
? getCompanyById(selectedCompanyId.value)
|
||||
? catalogStore.getCompany(selectedCompanyId.value)
|
||||
: null
|
||||
return selected?.maxMeteragePerInvoice ?? DEFAULT_MAX_METERAGE
|
||||
}
|
||||
|
||||
const companyIds = [...new Set(activeCart.value.items.map((item) => item.companyId))]
|
||||
return companyIds.reduce((sum, id) => {
|
||||
const company = getCompanyById(id)
|
||||
const company = catalogStore.getCompany(id)
|
||||
return sum + (company?.maxMeteragePerInvoice ?? DEFAULT_MAX_METERAGE)
|
||||
}, 0)
|
||||
})
|
||||
|
||||
const remainingCapacity = computed(() => Math.max(0, maxMeterage.value - totalMeterage.value))
|
||||
|
||||
const isFull = computed(() => totalMeterage.value >= maxMeterage.value)
|
||||
|
||||
function persist() {
|
||||
saveCarts(carts.value)
|
||||
saveActiveCartId(activeCartId.value)
|
||||
}
|
||||
|
||||
function createEmptyCart(company?: Company): Cart {
|
||||
function applyServerCart(serverCart: ApiCart) {
|
||||
const cart: Cart = {
|
||||
id: generateId(),
|
||||
companyId: company?.id ?? '',
|
||||
companyName: company?.name ?? '',
|
||||
items: [],
|
||||
id: serverCart.id,
|
||||
companyId: serverCart.companyId || '',
|
||||
companyName: serverCart.companyName || uniqueCompanyNames(serverCart.items),
|
||||
items: serverCart.items ?? [],
|
||||
notes: serverCart.notes,
|
||||
}
|
||||
carts.value.push(cart)
|
||||
carts.value = [cart]
|
||||
activeCartId.value = cart.id
|
||||
if (company) selectedCompanyId.value = company.id
|
||||
persist()
|
||||
return cart
|
||||
if (cart.companyId) selectedCompanyId.value = cart.companyId
|
||||
}
|
||||
|
||||
function getOrCreateCart(company?: Company): Cart {
|
||||
if (activeCart.value) return activeCart.value
|
||||
|
||||
const withItems = carts.value.find((cart) => cart.items.length > 0)
|
||||
if (withItems) {
|
||||
activeCartId.value = withItems.id
|
||||
selectedCompanyId.value = selectedCompanyId.value ?? withItems.companyId
|
||||
persist()
|
||||
return withItems
|
||||
async function hydrate() {
|
||||
if (!getAccessToken()) {
|
||||
resetLocal()
|
||||
return
|
||||
}
|
||||
|
||||
const open = carts.value.find(
|
||||
(cart) => getCartTotalMeterage(cart) < DEFAULT_MAX_METERAGE,
|
||||
)
|
||||
if (open) {
|
||||
activeCartId.value = open.id
|
||||
if (company) selectedCompanyId.value = company.id
|
||||
persist()
|
||||
return open
|
||||
syncing.value = true
|
||||
try {
|
||||
const serverCart = await apiRequest<ApiCart>('/carts')
|
||||
applyServerCart(serverCart)
|
||||
} catch {
|
||||
resetLocal()
|
||||
} finally {
|
||||
syncing.value = false
|
||||
}
|
||||
|
||||
return createEmptyCart(company)
|
||||
}
|
||||
|
||||
/** @deprecated Prefer getOrCreateCart / selectCompany — kept for callers. */
|
||||
function initCartForCompany(company: Company): Cart {
|
||||
selectedCompanyId.value = company.id
|
||||
const cart = getOrCreateCart(company)
|
||||
if (!cart.companyId) {
|
||||
cart.companyId = company.id
|
||||
cart.companyName = company.name
|
||||
persist()
|
||||
}
|
||||
return cart
|
||||
function resetLocal() {
|
||||
carts.value = [emptyCart()]
|
||||
activeCartId.value = carts.value[0]!.id
|
||||
selectedCompanyId.value = null
|
||||
}
|
||||
|
||||
function selectCompany(company: Company): void {
|
||||
selectedCompanyId.value = company.id
|
||||
getOrCreateCart(company)
|
||||
if (activeCart.value && !activeCart.value.items.length) {
|
||||
activeCart.value.companyId = company.id
|
||||
activeCart.value.companyName = company.name
|
||||
}
|
||||
}
|
||||
|
||||
function createNewCartForSameCompany(): Cart | null {
|
||||
const company = selectedCompanyId.value
|
||||
? getCompanyById(selectedCompanyId.value)
|
||||
: activeCart.value?.companyId
|
||||
? getCompanyById(activeCart.value.companyId)
|
||||
: undefined
|
||||
return createEmptyCart(company)
|
||||
function initCartForCompany(company: Company): Cart {
|
||||
selectCompany(company)
|
||||
return activeCart.value ?? emptyCart()
|
||||
}
|
||||
|
||||
function addItem(
|
||||
function getOrCreateCart(company?: Company): Cart {
|
||||
if (company) selectCompany(company)
|
||||
return activeCart.value ?? emptyCart()
|
||||
}
|
||||
|
||||
async function createNewCartForSameCompany(): Promise<Cart | null> {
|
||||
try {
|
||||
const serverCart = await apiRequest<ApiCart>('/carts', { method: 'DELETE' })
|
||||
applyServerCart(serverCart)
|
||||
return activeCart.value
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function addItem(
|
||||
product: Product,
|
||||
catalogCode: string,
|
||||
meterage: number,
|
||||
): { success: boolean; message?: string } {
|
||||
const company = getCompanyById(product.companyId)
|
||||
): Promise<{ success: boolean; message?: string }> {
|
||||
const company = catalogStore.getCompany(product.companyId)
|
||||
if (!company) {
|
||||
return { success: false, message: 'شرکت محصول یافت نشد.' }
|
||||
}
|
||||
|
||||
const cart = getOrCreateCart(company)
|
||||
selectedCompanyId.value = company.id
|
||||
|
||||
const companyMax = company.maxMeteragePerInvoice
|
||||
const currentCompanyMeterage = companyMeterage(cart, company.id)
|
||||
const existing = cart.items.find((i) => i.productId === product.id)
|
||||
@@ -192,41 +155,40 @@ export const useCartStore = defineStore('cart', () => {
|
||||
return { success: false, message: validation.message }
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
existing.meterage = meterage
|
||||
existing.catalogCode = catalogCode
|
||||
existing.companyId = company.id
|
||||
existing.companyName = company.name
|
||||
} else {
|
||||
cart.items.push({
|
||||
id: generateId(),
|
||||
productId: product.id,
|
||||
catalogCode,
|
||||
productName: product.name,
|
||||
productCode: product.code,
|
||||
color: product.color,
|
||||
meterage,
|
||||
unitPrice: product.basePrice,
|
||||
companyId: company.id,
|
||||
companyName: company.name,
|
||||
})
|
||||
try {
|
||||
let serverCart: ApiCart
|
||||
if (existing) {
|
||||
serverCart = await apiRequest<ApiCart>(`/carts/items/${existing.id}`, {
|
||||
method: 'PATCH',
|
||||
body: { meterage },
|
||||
})
|
||||
} else {
|
||||
serverCart = await apiRequest<ApiCart>('/carts/items', {
|
||||
method: 'POST',
|
||||
body: { productId: product.id, meterage },
|
||||
})
|
||||
}
|
||||
applyServerCart(serverCart)
|
||||
selectedCompanyId.value = company.id
|
||||
return { success: true }
|
||||
} catch (e) {
|
||||
return {
|
||||
success: false,
|
||||
message: e instanceof ApiError ? e.message : 'افزودن به سبد ناموفق بود.',
|
||||
}
|
||||
}
|
||||
|
||||
syncCartCompanyMeta(cart)
|
||||
persist()
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
function updateItemMeterage(
|
||||
async function updateItemMeterage(
|
||||
itemId: string,
|
||||
meterage: number,
|
||||
product: Product,
|
||||
): { success: boolean; message?: string } {
|
||||
): Promise<{ success: boolean; message?: string }> {
|
||||
if (!activeCart.value) return { success: false, message: 'سبد خرید یافت نشد.' }
|
||||
const item = activeCart.value.items.find((i) => i.id === itemId)
|
||||
if (!item) return { success: false, message: 'آیتم یافت نشد.' }
|
||||
|
||||
const company = getCompanyById(item.companyId || product.companyId)
|
||||
const company = catalogStore.getCompany(item.companyId || product.companyId)
|
||||
const companyMax = company?.maxMeteragePerInvoice ?? DEFAULT_MAX_METERAGE
|
||||
const currentCompanyMeterage = companyMeterage(activeCart.value, item.companyId)
|
||||
|
||||
@@ -241,56 +203,62 @@ export const useCartStore = defineStore('cart', () => {
|
||||
return { success: false, message: validation.message }
|
||||
}
|
||||
|
||||
item.meterage = meterage
|
||||
persist()
|
||||
return { success: true }
|
||||
try {
|
||||
const serverCart = await apiRequest<ApiCart>(`/carts/items/${itemId}`, {
|
||||
method: 'PATCH',
|
||||
body: { meterage },
|
||||
})
|
||||
applyServerCart(serverCart)
|
||||
return { success: true }
|
||||
} catch (e) {
|
||||
return {
|
||||
success: false,
|
||||
message: e instanceof ApiError ? e.message : 'بهروزرسانی متراژ ناموفق بود.',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeItem(itemId: string) {
|
||||
if (!activeCart.value) return
|
||||
activeCart.value.items = activeCart.value.items.filter((i) => i.id !== itemId)
|
||||
syncCartCompanyMeta(activeCart.value)
|
||||
persist()
|
||||
async function removeItem(itemId: string) {
|
||||
try {
|
||||
const serverCart = await apiRequest<ApiCart>(`/carts/items/${itemId}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
applyServerCart(serverCart)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
function setNotes(notes: string) {
|
||||
if (!activeCart.value) return
|
||||
activeCart.value.notes = notes
|
||||
persist()
|
||||
async function setNotes(notes: string) {
|
||||
try {
|
||||
const serverCart = await apiRequest<ApiCart>('/carts/notes', {
|
||||
method: 'PATCH',
|
||||
body: { notes },
|
||||
})
|
||||
applyServerCart(serverCart)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
function clearActiveCart() {
|
||||
if (!activeCart.value) return
|
||||
activeCart.value.items = []
|
||||
activeCart.value.notes = undefined
|
||||
persist()
|
||||
async function clearActiveCart() {
|
||||
try {
|
||||
const serverCart = await apiRequest<ApiCart>('/carts', { method: 'DELETE' })
|
||||
applyServerCart(serverCart)
|
||||
} catch {
|
||||
resetLocal()
|
||||
}
|
||||
}
|
||||
|
||||
function ensureActiveCartForCartPage() {
|
||||
if (activeCart.value?.items.length) return
|
||||
|
||||
const cartWithItems = carts.value.find((cart) => cart.items.length > 0)
|
||||
if (!cartWithItems) return
|
||||
|
||||
activeCartId.value = cartWithItems.id
|
||||
selectedCompanyId.value = selectedCompanyId.value || cartWithItems.companyId
|
||||
persist()
|
||||
}
|
||||
|
||||
function hydrate() {
|
||||
carts.value = migrateCartItems(loadCarts())
|
||||
activeCartId.value = loadActiveCartId()
|
||||
if (activeCart.value) {
|
||||
syncCartCompanyMeta(activeCart.value)
|
||||
selectedCompanyId.value = selectedCompanyId.value || activeCart.value.companyId
|
||||
persist()
|
||||
}
|
||||
// server keeps a single cart
|
||||
}
|
||||
|
||||
return {
|
||||
carts,
|
||||
activeCartId,
|
||||
selectedCompanyId,
|
||||
syncing,
|
||||
activeCart,
|
||||
totalMeterage,
|
||||
itemCount,
|
||||
@@ -308,5 +276,6 @@ export const useCartStore = defineStore('cart', () => {
|
||||
clearActiveCart,
|
||||
ensureActiveCartForCartPage,
|
||||
hydrate,
|
||||
resetLocal,
|
||||
}
|
||||
})
|
||||
|
||||
+81
-18
@@ -1,38 +1,86 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import {
|
||||
companies as mockCompanies,
|
||||
getCatalogsByCompany,
|
||||
getProductsByCatalog,
|
||||
getCatalogById,
|
||||
getProductById,
|
||||
getCompanyById,
|
||||
} from '@/data/mockData'
|
||||
import type { Catalog, Company, Product } from '@/types'
|
||||
import { apiRequest } from '@/services/http'
|
||||
import type { BankAccount, Catalog, Company, Product } from '@/types'
|
||||
|
||||
export const useCatalogStore = defineStore('catalog', () => {
|
||||
const companies = ref<Company[]>(mockCompanies.filter((c) => c.isActive))
|
||||
const companies = ref<Company[]>([])
|
||||
const catalogs = ref<Catalog[]>([])
|
||||
const products = ref<Product[]>([])
|
||||
const bankAccounts = ref<BankAccount[]>([])
|
||||
const searchQuery = ref('')
|
||||
const loading = ref(false)
|
||||
const loaded = ref(false)
|
||||
|
||||
async function loadCompanies(force = false) {
|
||||
if (loaded.value && !force) return companies.value
|
||||
loading.value = true
|
||||
try {
|
||||
const list = await apiRequest<Company[]>('/companies', {
|
||||
query: { activeOnly: true },
|
||||
})
|
||||
companies.value = list
|
||||
.filter((c) => c.isActive)
|
||||
.sort((a, b) => a.displayOrder - b.displayOrder)
|
||||
loaded.value = true
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
return companies.value
|
||||
}
|
||||
|
||||
function getActiveCompanies(): Company[] {
|
||||
return [...companies.value].sort((a, b) => a.displayOrder - b.displayOrder)
|
||||
}
|
||||
|
||||
function getCompany(id: string): Company | undefined {
|
||||
return getCompanyById(id)
|
||||
return companies.value.find((c) => c.id === id)
|
||||
}
|
||||
|
||||
async function loadCatalogs(companyId: string) {
|
||||
const list = await apiRequest<Catalog[]>('/catalogs', {
|
||||
query: { companyId, activeOnly: true },
|
||||
})
|
||||
catalogs.value = [
|
||||
...catalogs.value.filter((c) => c.companyId !== companyId),
|
||||
...list,
|
||||
]
|
||||
return list
|
||||
}
|
||||
|
||||
async function loadProducts(catalogId: string, color?: string) {
|
||||
const list = await apiRequest<Product[]>('/products', {
|
||||
query: {
|
||||
catalogId,
|
||||
color: color && color !== 'all' ? color : undefined,
|
||||
activeOnly: true,
|
||||
},
|
||||
})
|
||||
const others = products.value.filter((p) => p.catalogId !== catalogId)
|
||||
products.value = [...others, ...list]
|
||||
return list
|
||||
}
|
||||
|
||||
async function loadBankAccounts(companyId: string) {
|
||||
const list = await apiRequest<BankAccount[]>(`/companies/${companyId}/bank-accounts`)
|
||||
bankAccounts.value = [
|
||||
...bankAccounts.value.filter((b) => b.companyId !== companyId),
|
||||
...list.filter((b) => b.isActive),
|
||||
]
|
||||
return bankAccounts.value.filter((b) => b.companyId === companyId)
|
||||
}
|
||||
|
||||
function fetchCatalogs(companyId: string, query = ''): Catalog[] {
|
||||
let list = getCatalogsByCompany(companyId)
|
||||
let list = catalogs.value.filter((c) => c.companyId === companyId && c.isActive)
|
||||
if (query.trim()) {
|
||||
const q = query.trim()
|
||||
list = list.filter((c) => c.code.includes(q) || c.name.includes(q))
|
||||
}
|
||||
return list
|
||||
return [...list].sort((a, b) => a.displayOrder - b.displayOrder)
|
||||
}
|
||||
|
||||
function fetchProducts(catalogId: string, colorFilter?: string): Product[] {
|
||||
let list = getProductsByCatalog(catalogId)
|
||||
let list = products.value.filter((p) => p.catalogId === catalogId && p.isActive)
|
||||
if (colorFilter && colorFilter !== 'all') {
|
||||
list = list.filter((p) => p.color.includes(colorFilter))
|
||||
}
|
||||
@@ -40,21 +88,35 @@ export const useCatalogStore = defineStore('catalog', () => {
|
||||
}
|
||||
|
||||
function getCatalog(id: string) {
|
||||
return getCatalogById(id)
|
||||
return catalogs.value.find((c) => c.id === id)
|
||||
}
|
||||
|
||||
function getProduct(id: string) {
|
||||
return getProductById(id)
|
||||
return products.value.find((p) => p.id === id)
|
||||
}
|
||||
|
||||
function getUniqueColors(catalogId: string): string[] {
|
||||
const products = getProductsByCatalog(catalogId)
|
||||
return [...new Set(products.map((p) => p.color))]
|
||||
return [...new Set(fetchProducts(catalogId).map((p) => p.color))]
|
||||
}
|
||||
|
||||
function getBankAccountsByCompany(companyId: string): BankAccount[] {
|
||||
return bankAccounts.value
|
||||
.filter((b) => b.companyId === companyId && b.isActive)
|
||||
.sort((a, b) => a.displayOrder - b.displayOrder)
|
||||
}
|
||||
|
||||
return {
|
||||
companies,
|
||||
catalogs,
|
||||
products,
|
||||
bankAccounts,
|
||||
searchQuery,
|
||||
loading,
|
||||
loaded,
|
||||
loadCompanies,
|
||||
loadCatalogs,
|
||||
loadProducts,
|
||||
loadBankAccounts,
|
||||
getActiveCompanies,
|
||||
getCompany,
|
||||
fetchCatalogs,
|
||||
@@ -62,5 +124,6 @@ export const useCatalogStore = defineStore('catalog', () => {
|
||||
getCatalog,
|
||||
getProduct,
|
||||
getUniqueColors,
|
||||
getBankAccountsByCompany,
|
||||
}
|
||||
})
|
||||
|
||||
+121
-154
@@ -5,87 +5,29 @@ import type {
|
||||
CheckData,
|
||||
CashPaymentReceipt,
|
||||
Order,
|
||||
OrderItem,
|
||||
OrderStatus,
|
||||
OrderStatusHistoryEntry,
|
||||
SettlementType,
|
||||
CheckRegistrationMode,
|
||||
} from '@/types'
|
||||
import { generateId, generateOrderNumber } from '@/utils/format'
|
||||
import { generateId } from '@/utils/format'
|
||||
import { calculateCheckRass } from '@/utils/checkRass'
|
||||
import { getItem, setItem, STORAGE_KEYS } from '@/services/storage'
|
||||
import { getCompanyById, getBankAccountsByCompany } from '@/data/mockData'
|
||||
|
||||
function loadOrders(): Order[] {
|
||||
return getItem<Order[]>(STORAGE_KEYS.orders, [])
|
||||
}
|
||||
|
||||
function saveOrders(orders: Order[]) {
|
||||
setItem(STORAGE_KEYS.orders, orders)
|
||||
}
|
||||
|
||||
function createStatusEntry(
|
||||
from: OrderStatus | null,
|
||||
to: OrderStatus,
|
||||
note?: string,
|
||||
): OrderStatusHistoryEntry {
|
||||
return {
|
||||
id: generateId(),
|
||||
fromStatus: from,
|
||||
toStatus: to,
|
||||
changedAt: new Date().toISOString(),
|
||||
changedBy: 'customer',
|
||||
note,
|
||||
}
|
||||
}
|
||||
|
||||
function cartToOrderItems(cart: Cart): OrderItem[] {
|
||||
return cart.items.map((item) => ({
|
||||
id: generateId(),
|
||||
productId: item.productId,
|
||||
catalogCode: item.catalogCode,
|
||||
productName: item.productName,
|
||||
productCode: item.productCode,
|
||||
color: item.color,
|
||||
requestedMeterage: item.meterage,
|
||||
unitPrice: item.unitPrice,
|
||||
notes: item.notes,
|
||||
}))
|
||||
}
|
||||
|
||||
function computeCashExpiry(companyId: string): string {
|
||||
const company = getCompanyById(companyId)
|
||||
const now = new Date()
|
||||
if (company?.cashInvoiceValidUntilHour) {
|
||||
const expiry = new Date(now)
|
||||
expiry.setHours(company.cashInvoiceValidUntilHour, 0, 0, 0)
|
||||
if (expiry <= now) {
|
||||
expiry.setDate(expiry.getDate() + 1)
|
||||
}
|
||||
return expiry.toISOString()
|
||||
}
|
||||
const hours = company?.cashInvoiceValidityHours ?? 8
|
||||
return new Date(now.getTime() + hours * 60 * 60 * 1000).toISOString()
|
||||
}
|
||||
import { apiRequest, ApiError, getAccessToken } from '@/services/http'
|
||||
import { useCatalogStore } from './catalog'
|
||||
|
||||
export const useOrderStore = defineStore('order', () => {
|
||||
const orders = ref<Order[]>(loadOrders())
|
||||
const orders = ref<Order[]>([])
|
||||
const draftChecks = ref<CheckData[]>([])
|
||||
const draftSettlementType = ref<SettlementType | null>(null)
|
||||
const draftCheckMode = ref<CheckRegistrationMode | null>(null)
|
||||
const checkoutCartId = ref<string | null>(null)
|
||||
const loading = ref(false)
|
||||
|
||||
const userOrders = computed(
|
||||
() => (userId: string) =>
|
||||
orders.value
|
||||
.filter((o) => o.userId === userId)
|
||||
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()),
|
||||
() => (_userId: string) =>
|
||||
[...orders.value].sort(
|
||||
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
|
||||
),
|
||||
)
|
||||
|
||||
function persist() {
|
||||
saveOrders(orders.value)
|
||||
}
|
||||
|
||||
function getOrderById(id: string): Order | undefined {
|
||||
return orders.value.find((o) => o.id === id)
|
||||
}
|
||||
@@ -120,108 +62,131 @@ export const useOrderStore = defineStore('order', () => {
|
||||
return calculateCheckRass(draftChecks.value, baseDate ?? new Date())
|
||||
}
|
||||
|
||||
function submitOrder(
|
||||
userId: string,
|
||||
cart: Cart,
|
||||
settlementType: SettlementType,
|
||||
checks?: CheckData[],
|
||||
checkMode?: CheckRegistrationMode,
|
||||
): Order {
|
||||
const items = cartToOrderItems(cart)
|
||||
const totalMeterage = items.reduce((s, i) => s + i.requestedMeterage, 0)
|
||||
const now = new Date().toISOString()
|
||||
|
||||
let status: OrderStatus = 'awaiting_review'
|
||||
let checkRassDays: number | undefined
|
||||
let checkRassDate: string | undefined
|
||||
let cashInvoiceExpiresAt: string | undefined
|
||||
|
||||
if (settlementType === 'check' && checks && checks.length > 0) {
|
||||
const rass = calculateCheckRass(checks, new Date())
|
||||
checkRassDays = rass.rassDays
|
||||
checkRassDate = rass.rassDate ?? undefined
|
||||
status = 'awaiting_review'
|
||||
async function hydrate() {
|
||||
if (!getAccessToken()) {
|
||||
orders.value = []
|
||||
return
|
||||
}
|
||||
|
||||
if (settlementType === 'cash') {
|
||||
cashInvoiceExpiresAt = computeCashExpiry(cart.companyId)
|
||||
status = 'awaiting_review'
|
||||
loading.value = true
|
||||
try {
|
||||
orders.value = await apiRequest<Order[]>('/orders')
|
||||
} catch {
|
||||
orders.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const order: Order = {
|
||||
id: generateId(),
|
||||
orderNumber: generateOrderNumber(),
|
||||
userId,
|
||||
companyId: cart.companyId,
|
||||
companyName: cart.companyName,
|
||||
items,
|
||||
totalMeterage,
|
||||
settlementType,
|
||||
checkMode,
|
||||
checks: checks ? [...checks] : undefined,
|
||||
checkRassDays,
|
||||
checkRassDate,
|
||||
status,
|
||||
notes: cart.notes,
|
||||
cashInvoiceExpiresAt,
|
||||
statusHistory: [
|
||||
createStatusEntry(null, 'submitted', 'سفارش ثبت شد'),
|
||||
createStatusEntry('submitted', status, 'ارسال برای بررسی مدیریت'),
|
||||
],
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}
|
||||
|
||||
orders.value.unshift(order)
|
||||
persist()
|
||||
|
||||
function resetLocal() {
|
||||
orders.value = []
|
||||
draftChecks.value = []
|
||||
draftSettlementType.value = null
|
||||
draftCheckMode.value = null
|
||||
checkoutCartId.value = null
|
||||
|
||||
return order
|
||||
}
|
||||
|
||||
function submitCashReceipt(
|
||||
async function fetchOrder(id: string): Promise<Order | null> {
|
||||
try {
|
||||
const order = await apiRequest<Order>(`/orders/${id}`)
|
||||
const idx = orders.value.findIndex((o) => o.id === id)
|
||||
if (idx >= 0) orders.value[idx] = order
|
||||
else orders.value.unshift(order)
|
||||
return order
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function submitOrder(
|
||||
_userId: string,
|
||||
cart: Cart,
|
||||
settlementType: SettlementType,
|
||||
checks?: CheckData[],
|
||||
checkMode?: CheckRegistrationMode,
|
||||
): Promise<Order> {
|
||||
if (!cart.companyId) {
|
||||
throw new Error('شرکت سبد مشخص نیست.')
|
||||
}
|
||||
if (!cart.items.length) {
|
||||
throw new Error('سبد خرید خالی است.')
|
||||
}
|
||||
|
||||
const body = {
|
||||
companyId: cart.companyId,
|
||||
settlementType,
|
||||
checkMode: settlementType === 'check' ? (checkMode ?? 'full') : undefined,
|
||||
notes: cart.notes,
|
||||
items: cart.items.map((item) => ({
|
||||
productId: item.productId,
|
||||
meterage: item.meterage,
|
||||
notes: item.notes,
|
||||
})),
|
||||
checks:
|
||||
settlementType === 'check' && checks
|
||||
? checks.map(({ id: _id, ...rest }) => rest)
|
||||
: undefined,
|
||||
}
|
||||
|
||||
try {
|
||||
const order = await apiRequest<Order>('/orders', {
|
||||
method: 'POST',
|
||||
body,
|
||||
})
|
||||
orders.value.unshift(order)
|
||||
draftChecks.value = []
|
||||
draftSettlementType.value = null
|
||||
draftCheckMode.value = null
|
||||
checkoutCartId.value = null
|
||||
return order
|
||||
} catch (e) {
|
||||
throw new Error(e instanceof ApiError ? e.message : 'ثبت سفارش ناموفق بود.')
|
||||
}
|
||||
}
|
||||
|
||||
async function submitCashReceipt(
|
||||
orderId: string,
|
||||
receipt: Omit<CashPaymentReceipt, 'id' | 'submittedAt'>,
|
||||
) {
|
||||
const order = getOrderById(orderId)
|
||||
if (!order) return null
|
||||
|
||||
const fullReceipt: CashPaymentReceipt = {
|
||||
...receipt,
|
||||
id: generateId(),
|
||||
submittedAt: new Date().toISOString(),
|
||||
try {
|
||||
const order = await apiRequest<Order>(`/orders/${orderId}/cash-receipt`, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
amount: receipt.amount,
|
||||
transferDate: receipt.transferDate,
|
||||
transferTime: receipt.transferTime,
|
||||
trackingNumber: receipt.trackingNumber,
|
||||
destinationAccountId: receipt.destinationAccountId,
|
||||
receiptImage: receipt.receiptImage,
|
||||
notes: receipt.notes,
|
||||
},
|
||||
})
|
||||
const idx = orders.value.findIndex((o) => o.id === orderId)
|
||||
if (idx >= 0) orders.value[idx] = order
|
||||
else orders.value.unshift(order)
|
||||
return order
|
||||
} catch (e) {
|
||||
throw new Error(e instanceof ApiError ? e.message : 'ثبت رسید ناموفق بود.')
|
||||
}
|
||||
|
||||
const prevStatus = order.status
|
||||
order.cashReceipt = fullReceipt
|
||||
order.status = 'awaiting_cash_confirmation'
|
||||
order.statusHistory.push(
|
||||
createStatusEntry(prevStatus, 'awaiting_cash_confirmation', 'رسید پرداخت ثبت شد'),
|
||||
)
|
||||
order.updatedAt = new Date().toISOString()
|
||||
persist()
|
||||
return order
|
||||
}
|
||||
|
||||
function updateOrderChecks(orderId: string, checks: CheckData[]) {
|
||||
const order = getOrderById(orderId)
|
||||
if (!order) return null
|
||||
|
||||
const rass = calculateCheckRass(checks, new Date(order.createdAt))
|
||||
order.checks = checks
|
||||
order.checkRassDays = rass.rassDays
|
||||
order.checkRassDate = rass.rassDate ?? undefined
|
||||
order.updatedAt = new Date().toISOString()
|
||||
persist()
|
||||
return order
|
||||
async function updateOrderChecks(orderId: string, checks: CheckData[]) {
|
||||
try {
|
||||
const order = await apiRequest<Order>(`/orders/${orderId}/checks`, {
|
||||
method: 'PATCH',
|
||||
body: {
|
||||
checks: checks.map(({ id: _id, ...rest }) => rest),
|
||||
},
|
||||
})
|
||||
const idx = orders.value.findIndex((o) => o.id === orderId)
|
||||
if (idx >= 0) orders.value[idx] = order
|
||||
return order
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function hydrate() {
|
||||
orders.value = loadOrders()
|
||||
async function getBankAccountsForCompany(companyId: string) {
|
||||
return useCatalogStore().loadBankAccounts(companyId)
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -230,8 +195,8 @@ export const useOrderStore = defineStore('order', () => {
|
||||
draftSettlementType,
|
||||
draftCheckMode,
|
||||
checkoutCartId,
|
||||
loading,
|
||||
userOrders,
|
||||
persist,
|
||||
getOrderById,
|
||||
startCheckout,
|
||||
setCheckMode,
|
||||
@@ -243,6 +208,8 @@ export const useOrderStore = defineStore('order', () => {
|
||||
submitCashReceipt,
|
||||
updateOrderChecks,
|
||||
hydrate,
|
||||
getBankAccountsForCompany: getBankAccountsByCompany,
|
||||
resetLocal,
|
||||
fetchOrder,
|
||||
getBankAccountsForCompany,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, watchEffect } from 'vue'
|
||||
import { usePreferredDark, useStorage } from '@vueuse/core'
|
||||
|
||||
export type ThemePreference = 'light' | 'dark' | 'system'
|
||||
|
||||
export const useThemeStore = defineStore('theme', () => {
|
||||
const preference = useStorage<ThemePreference>('ft_theme', 'system')
|
||||
const prefersDark = usePreferredDark()
|
||||
|
||||
const resolved = computed<'light' | 'dark'>(() => {
|
||||
if (preference.value === 'system') return prefersDark.value ? 'dark' : 'light'
|
||||
return preference.value
|
||||
})
|
||||
|
||||
const isDark = computed(() => resolved.value === 'dark')
|
||||
|
||||
watchEffect(() => {
|
||||
const root = document.documentElement
|
||||
root.classList.toggle('dark', isDark.value)
|
||||
root.style.colorScheme = resolved.value
|
||||
})
|
||||
|
||||
function setPreference(value: ThemePreference) {
|
||||
preference.value = value
|
||||
}
|
||||
|
||||
return {
|
||||
preference,
|
||||
resolved,
|
||||
isDark,
|
||||
setPreference,
|
||||
}
|
||||
})
|
||||
@@ -72,6 +72,7 @@ export interface Catalog {
|
||||
description?: string
|
||||
displayOrder: number
|
||||
isActive: boolean
|
||||
productCount?: number
|
||||
}
|
||||
|
||||
export interface Product {
|
||||
|
||||
+27
-14
@@ -1,20 +1,22 @@
|
||||
import type { CheckData, CheckRassResult } from '@/types'
|
||||
import { parseISODate } from '@/utils/jalali'
|
||||
import { parseISODate, toISODate } from '@/utils/jalali'
|
||||
|
||||
function emptyRass(): CheckRassResult {
|
||||
return {
|
||||
checkCount: 0,
|
||||
totalAmount: 0,
|
||||
nearestDueDate: null,
|
||||
farthestDueDate: null,
|
||||
rassDays: 0,
|
||||
rassDate: null,
|
||||
}
|
||||
}
|
||||
|
||||
export function calculateCheckRass(
|
||||
checks: CheckData[],
|
||||
baseDate: Date = new Date(),
|
||||
): CheckRassResult {
|
||||
if (checks.length === 0) {
|
||||
return {
|
||||
checkCount: 0,
|
||||
totalAmount: 0,
|
||||
nearestDueDate: null,
|
||||
farthestDueDate: null,
|
||||
rassDays: 0,
|
||||
rassDate: null,
|
||||
}
|
||||
}
|
||||
if (checks.length === 0) return emptyRass()
|
||||
|
||||
const base = new Date(baseDate)
|
||||
base.setHours(0, 0, 0, 0)
|
||||
@@ -24,7 +26,17 @@ export function calculateCheckRass(
|
||||
const dueDates: Date[] = []
|
||||
|
||||
for (const check of checks) {
|
||||
if (!check.dueDate) {
|
||||
totalAmount += check.amount
|
||||
continue
|
||||
}
|
||||
|
||||
const due = parseISODate(check.dueDate)
|
||||
if (Number.isNaN(due.getTime())) {
|
||||
totalAmount += check.amount
|
||||
continue
|
||||
}
|
||||
|
||||
due.setHours(0, 0, 0, 0)
|
||||
dueDates.push(due)
|
||||
const diffMs = due.getTime() - base.getTime()
|
||||
@@ -42,10 +54,10 @@ export function calculateCheckRass(
|
||||
return {
|
||||
checkCount: checks.length,
|
||||
totalAmount,
|
||||
nearestDueDate: dueDates[0]?.toISOString() ?? null,
|
||||
farthestDueDate: dueDates[dueDates.length - 1]?.toISOString() ?? null,
|
||||
nearestDueDate: dueDates[0] ? toISODate(dueDates[0]) : null,
|
||||
farthestDueDate: dueDates.length ? toISODate(dueDates[dueDates.length - 1]!) : null,
|
||||
rassDays,
|
||||
rassDate: rassDate.toISOString(),
|
||||
rassDate: toISODate(rassDate),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +65,7 @@ export function daysUntilDue(dueDate: string): number {
|
||||
const base = new Date()
|
||||
base.setHours(0, 0, 0, 0)
|
||||
const due = parseISODate(dueDate)
|
||||
if (Number.isNaN(due.getTime())) return 0
|
||||
due.setHours(0, 0, 0, 0)
|
||||
return Math.max(0, Math.round((due.getTime() - base.getTime()) / (1000 * 60 * 60 * 24)))
|
||||
}
|
||||
|
||||
+15
-9
@@ -45,16 +45,22 @@ const persianDateTimeFormatter = new Intl.DateTimeFormat('fa-IR-u-ca-persian', {
|
||||
|
||||
function toDateValue(date: string | Date): Date {
|
||||
if (typeof date !== 'string') return date
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(date)) return parseISODate(date)
|
||||
if (/^\d{4}-\d{2}-\d{2}/.test(date)) return parseISODate(date)
|
||||
return new Date(date)
|
||||
}
|
||||
|
||||
export function formatDate(date: string | Date): string {
|
||||
return persianDateFormatter.format(toDateValue(date))
|
||||
export function formatDate(date: string | Date | null | undefined): string {
|
||||
if (!date) return '—'
|
||||
const value = toDateValue(date)
|
||||
if (Number.isNaN(value.getTime())) return '—'
|
||||
return persianDateFormatter.format(value)
|
||||
}
|
||||
|
||||
export function formatDateTime(date: string | Date): string {
|
||||
return persianDateTimeFormatter.format(toDateValue(date))
|
||||
export function formatDateTime(date: string | Date | null | undefined): string {
|
||||
if (!date) return '—'
|
||||
const value = toDateValue(date)
|
||||
if (Number.isNaN(value.getTime())) return '—'
|
||||
return persianDateTimeFormatter.format(value)
|
||||
}
|
||||
|
||||
export function formatTime(date: string | Date): string {
|
||||
@@ -70,10 +76,10 @@ export function toPersianDigits(input: string): string {
|
||||
}
|
||||
|
||||
export function normalizePhone(phone: string): string {
|
||||
const digits = phone.replace(/\D/g, '')
|
||||
if (digits.startsWith('98')) return '0' + digits.slice(2)
|
||||
if (digits.startsWith('9') && digits.length === 10) return '0' + digits
|
||||
return digits
|
||||
let digits = extractNumericString(phone)
|
||||
if (digits.startsWith('98')) digits = '0' + digits.slice(2)
|
||||
else if (digits.startsWith('9') && digits.length === 10) digits = '0' + digits
|
||||
return digits.slice(0, 11)
|
||||
}
|
||||
|
||||
export function generateId(): string {
|
||||
|
||||
+11
-2
@@ -30,8 +30,17 @@ export interface JalaliDate {
|
||||
}
|
||||
|
||||
export function parseISODate(iso: string): Date {
|
||||
const [year, month, day] = iso.split('-').map(Number)
|
||||
return new Date(year!, month! - 1, day!)
|
||||
const match = iso.trim().match(/^(\d{4})-(\d{2})-(\d{2})/)
|
||||
if (match) {
|
||||
return new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]))
|
||||
}
|
||||
|
||||
const parsed = new Date(iso)
|
||||
if (Number.isNaN(parsed.getTime())) {
|
||||
return new Date(NaN)
|
||||
}
|
||||
|
||||
return new Date(parsed.getFullYear(), parsed.getMonth(), parsed.getDate())
|
||||
}
|
||||
|
||||
export function toISODate(date: Date): string {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { MeterageValidationResult, Product } from '@/types'
|
||||
import { normalizePhone } from '@/utils/format'
|
||||
|
||||
export function validateNationalId(nationalId: string): boolean {
|
||||
if (!/^\d{10}$/.test(nationalId)) return false
|
||||
@@ -17,7 +18,7 @@ export function validateSayadId(sayadId: string): boolean {
|
||||
}
|
||||
|
||||
export function validatePhone(phone: string): boolean {
|
||||
return /^09\d{9}$/.test(phone.replace(/\D/g, '').replace(/^9/, '09').slice(0, 11))
|
||||
return /^09\d{9}$/.test(normalizePhone(phone))
|
||||
}
|
||||
|
||||
export function validateMeterage(
|
||||
|
||||
@@ -14,14 +14,20 @@ const {
|
||||
filteredOrdersMobile,
|
||||
filteredOrdersDesktop,
|
||||
paginatedOrders,
|
||||
paginatedOrdersMobile,
|
||||
totalPages,
|
||||
mobileTotalPages,
|
||||
paginationRange,
|
||||
mobilePaginationRange,
|
||||
visiblePages,
|
||||
mobileVisiblePages,
|
||||
currentPage,
|
||||
mobilePage,
|
||||
companySlug,
|
||||
iconVariant,
|
||||
goToOrder,
|
||||
setPage,
|
||||
setMobilePage,
|
||||
setDesktopFilter,
|
||||
viewStatFilter,
|
||||
} = useOrderList()
|
||||
@@ -47,12 +53,18 @@ const desktopFilterTabs: { id: DesktopOrderFilterId; label: string }[] = [
|
||||
class="mobile-only"
|
||||
:tab="tab"
|
||||
:tabs="mobileTabs"
|
||||
:orders="filteredOrdersMobile"
|
||||
:orders="paginatedOrdersMobile"
|
||||
:total-count="filteredOrdersMobile.length"
|
||||
:current-page="mobilePage"
|
||||
:total-pages="mobileTotalPages"
|
||||
:pagination-range="mobilePaginationRange"
|
||||
:visible-pages="mobileVisiblePages"
|
||||
:company-slug="companySlug"
|
||||
:icon-variant="iconVariant"
|
||||
@update:tab="tab = $event"
|
||||
@select="goToOrder"
|
||||
@back="router.push('/')"
|
||||
@set-page="setMobilePage"
|
||||
/>
|
||||
|
||||
<DesktopOrdersPage
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import BaseInput from '@/components/ui/BaseInput.vue'
|
||||
import FormErrorBanner from '@/components/ui/FormErrorBanner.vue'
|
||||
import BaseButton from '@/components/ui/BaseButton.vue'
|
||||
import { normalizePhone } from '@/utils/format'
|
||||
import { normalizePhone, toPersianDigits } from '@/utils/format'
|
||||
import { validatePhone } from '@/utils/validation'
|
||||
import {
|
||||
FORM_VALIDATION_SUMMARY,
|
||||
clearFieldError,
|
||||
@@ -20,24 +21,42 @@ const phone = ref('')
|
||||
const fieldErrors = ref<FieldErrors>({})
|
||||
const formError = ref('')
|
||||
|
||||
const displayPhone = computed(() => (phone.value ? toPersianDigits(phone.value) : ''))
|
||||
|
||||
function phoneErrorMessage(value: string): string {
|
||||
if (!value) return 'شماره موبایل را وارد کنید.'
|
||||
if (!validatePhone(value)) return 'شماره موبایل معتبر نیست. (مثال: ۰۹۱۲۱۲۳۴۵۶۷)'
|
||||
return ''
|
||||
}
|
||||
|
||||
function clearPhoneError() {
|
||||
fieldErrors.value = clearFieldError(fieldErrors.value, 'phone')
|
||||
if (!hasFieldErrors(fieldErrors.value)) formError.value = ''
|
||||
}
|
||||
|
||||
function onPhoneInput(value: string) {
|
||||
phone.value = normalizePhone(value)
|
||||
clearPhoneError()
|
||||
}
|
||||
|
||||
function onPhoneBlur() {
|
||||
if (!phone.value) return
|
||||
const message = phoneErrorMessage(phone.value)
|
||||
if (message) {
|
||||
fieldErrors.value = { ...fieldErrors.value, phone: message }
|
||||
}
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
fieldErrors.value = {}
|
||||
formError.value = ''
|
||||
|
||||
const normalized = normalizePhone(phone.value)
|
||||
const errors: FieldErrors = {}
|
||||
phone.value = normalized
|
||||
|
||||
if (!/^09\d{9}$/.test(normalized)) {
|
||||
errors.phone = 'شماره موبایل معتبر نیست.'
|
||||
}
|
||||
|
||||
if (hasFieldErrors(errors)) {
|
||||
fieldErrors.value = errors
|
||||
const message = phoneErrorMessage(normalized)
|
||||
if (message) {
|
||||
fieldErrors.value = { phone: message }
|
||||
formError.value = FORM_VALIDATION_SUMMARY
|
||||
return
|
||||
}
|
||||
@@ -52,59 +71,41 @@ async function submit() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page auth-page">
|
||||
<div class="page__scroll auth-page__scroll">
|
||||
<div class="page page-shell auth-page">
|
||||
<div class="page__scroll flex flex-col justify-center py-6">
|
||||
<div class="auth-header">
|
||||
<div class="logo">🧵</div>
|
||||
<div class="text-5xl mb-3">🧵</div>
|
||||
<h1 class="page-title">ورود به سامانه</h1>
|
||||
<p class="page-subtitle">سامانه سفارش عمده پارچه</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<form class="card" @submit.prevent="submit">
|
||||
<FormErrorBanner :message="formError" />
|
||||
|
||||
<BaseInput
|
||||
v-model="phone"
|
||||
:model-value="displayPhone"
|
||||
label="شماره موبایل"
|
||||
type="tel"
|
||||
inputmode="numeric"
|
||||
autocomplete="tel"
|
||||
dir="ltr"
|
||||
align="center"
|
||||
maxlength="11"
|
||||
placeholder="۰۹۱۲۳۴۵۶۷۸۹"
|
||||
:error="fieldErrors.phone"
|
||||
hint="کد تأیید به این شماره ارسال میشود"
|
||||
@update:model-value="clearPhoneError"
|
||||
@update:model-value="onPhoneInput"
|
||||
@blur="onPhoneBlur"
|
||||
/>
|
||||
<BaseButton block :loading="auth.loading" @click="submit">دریافت کد تأیید</BaseButton>
|
||||
</div>
|
||||
<BaseButton block type="submit" :loading="auth.loading">دریافت کد تأیید</BaseButton>
|
||||
</form>
|
||||
|
||||
<p class="auth-footer text-center text-sm">
|
||||
حساب کاربری ندارید؟
|
||||
<router-link to="/register">ثبتنام</router-link>
|
||||
</p>
|
||||
<div class="mt-6 w-full max-w-[var(--form-max-width)]">
|
||||
<p class="m-0 mb-2.5 text-center text-sm text-muted">حساب کاربری ندارید؟</p>
|
||||
<RouterLink v-slot="{ navigate }" to="/register" custom>
|
||||
<BaseButton block variant="outline" type="button" @click="navigate">ثبتنام</BaseButton>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.auth-page {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.auth-page__scroll {
|
||||
padding-top: 36px;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.auth-header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.auth-footer {
|
||||
margin-top: 24px;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
</style>
|
||||
|
||||
+237
-34
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, onMounted, nextTick } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useIntervalFn } from '@vueuse/core'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import BaseInput from '@/components/ui/BaseInput.vue'
|
||||
import FormErrorBanner from '@/components/ui/FormErrorBanner.vue'
|
||||
import BaseButton from '@/components/ui/BaseButton.vue'
|
||||
import { toPersianDigits, extractNumericString } from '@/utils/format'
|
||||
import {
|
||||
FORM_VALIDATION_SUMMARY,
|
||||
clearFieldError,
|
||||
@@ -12,27 +13,155 @@ import {
|
||||
type FieldErrors,
|
||||
} from '@/utils/formErrors'
|
||||
|
||||
const OTP_LENGTH = 6
|
||||
const RESEND_SECONDS = 60
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
|
||||
const phone = computed(() => (route.query.phone as string) ?? '')
|
||||
const code = ref('')
|
||||
const digits = ref<string[]>(Array.from({ length: OTP_LENGTH }, () => ''))
|
||||
const inputRefs = ref<(HTMLInputElement | null)[]>([])
|
||||
const fieldErrors = ref<FieldErrors>({})
|
||||
const formError = ref('')
|
||||
const secondsLeft = ref(RESEND_SECONDS)
|
||||
|
||||
const code = computed(() => digits.value.join(''))
|
||||
const canResend = computed(() => secondsLeft.value <= 0 && !auth.loading)
|
||||
const timerLabel = computed(() => {
|
||||
const minutes = Math.floor(secondsLeft.value / 60)
|
||||
const seconds = secondsLeft.value % 60
|
||||
const formatted = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
|
||||
return toPersianDigits(formatted)
|
||||
})
|
||||
|
||||
const { pause, resume } = useIntervalFn(
|
||||
() => {
|
||||
if (secondsLeft.value <= 1) {
|
||||
secondsLeft.value = 0
|
||||
pause()
|
||||
return
|
||||
}
|
||||
secondsLeft.value -= 1
|
||||
},
|
||||
1000,
|
||||
{ immediate: false },
|
||||
)
|
||||
|
||||
function clearCodeError() {
|
||||
fieldErrors.value = clearFieldError(fieldErrors.value, 'code')
|
||||
if (!hasFieldErrors(fieldErrors.value)) formError.value = ''
|
||||
}
|
||||
|
||||
function setInputRef(el: unknown, index: number) {
|
||||
inputRefs.value[index] = el as HTMLInputElement | null
|
||||
}
|
||||
|
||||
function focusInput(index: number) {
|
||||
const clamped = Math.max(0, Math.min(OTP_LENGTH - 1, index))
|
||||
void nextTick(() => inputRefs.value[clamped]?.focus())
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
secondsLeft.value = RESEND_SECONDS
|
||||
resume()
|
||||
}
|
||||
|
||||
function fillDigits(value: string, startIndex = 0) {
|
||||
const numeric = extractNumericString(value).slice(0, OTP_LENGTH - startIndex)
|
||||
if (!numeric) return startIndex
|
||||
|
||||
const next = [...digits.value]
|
||||
let cursor = startIndex
|
||||
for (const digit of numeric) {
|
||||
if (cursor >= OTP_LENGTH) break
|
||||
next[cursor] = digit
|
||||
cursor += 1
|
||||
}
|
||||
digits.value = next
|
||||
clearCodeError()
|
||||
return Math.min(cursor, OTP_LENGTH - 1)
|
||||
}
|
||||
|
||||
function tryAutoSubmit() {
|
||||
if (code.value.length === OTP_LENGTH && !auth.loading) {
|
||||
void submit()
|
||||
}
|
||||
}
|
||||
|
||||
function onDigitInput(index: number, event: Event) {
|
||||
const target = event.target as HTMLInputElement
|
||||
const numeric = extractNumericString(target.value)
|
||||
|
||||
if (numeric.length > 1) {
|
||||
const focusAt = fillDigits(numeric, index)
|
||||
target.value = digits.value[index] ?? ''
|
||||
focusInput(focusAt)
|
||||
tryAutoSubmit()
|
||||
return
|
||||
}
|
||||
|
||||
const digit = numeric.slice(-1)
|
||||
digits.value[index] = digit
|
||||
target.value = digit
|
||||
clearCodeError()
|
||||
|
||||
if (digit && index < OTP_LENGTH - 1) {
|
||||
focusInput(index + 1)
|
||||
} else if (digit) {
|
||||
tryAutoSubmit()
|
||||
}
|
||||
}
|
||||
|
||||
function onDigitKeydown(index: number, event: KeyboardEvent) {
|
||||
if (event.key === 'Backspace') {
|
||||
if (digits.value[index]) {
|
||||
digits.value[index] = ''
|
||||
clearCodeError()
|
||||
return
|
||||
}
|
||||
if (index > 0) {
|
||||
digits.value[index - 1] = ''
|
||||
clearCodeError()
|
||||
focusInput(index - 1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowLeft') {
|
||||
event.preventDefault()
|
||||
focusInput(index - 1)
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowRight') {
|
||||
event.preventDefault()
|
||||
focusInput(index + 1)
|
||||
}
|
||||
}
|
||||
|
||||
function onDigitPaste(index: number, event: ClipboardEvent) {
|
||||
event.preventDefault()
|
||||
const pasted = event.clipboardData?.getData('text') ?? ''
|
||||
const focusAt = fillDigits(pasted, index)
|
||||
focusInput(focusAt)
|
||||
tryAutoSubmit()
|
||||
}
|
||||
|
||||
function displayDigit(value: string) {
|
||||
return value ? toPersianDigits(value) : ''
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
if (auth.loading) return
|
||||
|
||||
fieldErrors.value = {}
|
||||
formError.value = ''
|
||||
|
||||
const errors: FieldErrors = {}
|
||||
if (code.value.length < 4) {
|
||||
errors.code = 'کد تأیید را وارد کنید.'
|
||||
if (code.value.length < OTP_LENGTH) {
|
||||
errors.code = 'کد تأیید را کامل وارد کنید.'
|
||||
}
|
||||
|
||||
if (hasFieldErrors(errors)) {
|
||||
@@ -46,55 +175,129 @@ async function submit() {
|
||||
router.replace('/')
|
||||
} else {
|
||||
formError.value = result.message ?? 'کد تأیید نامعتبر است.'
|
||||
digits.value = Array.from({ length: OTP_LENGTH }, () => '')
|
||||
focusInput(0)
|
||||
}
|
||||
}
|
||||
|
||||
async function resend() {
|
||||
if (!canResend.value) return
|
||||
|
||||
const isRegister = route.query.mode === 'register'
|
||||
await auth.requestOtp(phone.value, { isRegister })
|
||||
const result = await auth.requestOtp(phone.value, { isRegister })
|
||||
if (result.success) {
|
||||
digits.value = Array.from({ length: OTP_LENGTH }, () => '')
|
||||
formError.value = ''
|
||||
fieldErrors.value = {}
|
||||
startTimer()
|
||||
focusInput(0)
|
||||
} else {
|
||||
formError.value = result.message ?? 'خطا در ارسال مجدد کد.'
|
||||
}
|
||||
}
|
||||
|
||||
function changePhone() {
|
||||
const isRegister = route.query.mode === 'register'
|
||||
router.replace({ name: isRegister ? 'register' : 'login' })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
startTimer()
|
||||
focusInput(0)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page auth-page">
|
||||
<div class="page__scroll auth-page__scroll">
|
||||
<div class="page page-shell auth-page">
|
||||
<div class="page__scroll flex flex-col justify-center py-6">
|
||||
<div class="auth-header">
|
||||
<h1 class="page-title">تأیید شماره موبایل</h1>
|
||||
<p class="page-subtitle">کد ارسالشده به {{ phone }} را وارد کنید</p>
|
||||
<p class="page-subtitle">کد ارسالشده به {{ toPersianDigits(phone) }} را وارد کنید</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<form class="card" @submit.prevent="submit">
|
||||
<FormErrorBanner :message="formError" />
|
||||
|
||||
<BaseInput
|
||||
v-model="code"
|
||||
label="کد تأیید"
|
||||
type="text"
|
||||
placeholder="۱۲۳۴۵۶"
|
||||
maxlength="6"
|
||||
:error="fieldErrors.code"
|
||||
hint="در نسخه آزمایشی: 123456"
|
||||
@update:model-value="clearCodeError"
|
||||
/>
|
||||
<BaseButton block :loading="auth.loading" @click="submit">تأیید و ورود</BaseButton>
|
||||
<BaseButton block variant="ghost" class="mt-2" @click="resend">ارسال مجدد کد</BaseButton>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="otp-0">کد تأیید</label>
|
||||
<div class="flex justify-center gap-2.5 ltr" dir="ltr" role="group" aria-label="کد تأیید">
|
||||
<input
|
||||
v-for="(digit, index) in digits"
|
||||
:id="`otp-${index}`"
|
||||
:key="index"
|
||||
:ref="(el) => setInputRef(el, index)"
|
||||
class="otp-box"
|
||||
:class="{ 'otp-box--error': fieldErrors.code }"
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
autocomplete="one-time-code"
|
||||
maxlength="1"
|
||||
:value="displayDigit(digit)"
|
||||
:aria-label="`رقم ${toPersianDigits(String(index + 1))}`"
|
||||
@input="onDigitInput(index, $event)"
|
||||
@keydown="onDigitKeydown(index, $event)"
|
||||
@paste="onDigitPaste(index, $event)"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="fieldErrors.code" class="input-error-text">{{ fieldErrors.code }}</p>
|
||||
</div>
|
||||
|
||||
<BaseButton block type="submit" :loading="auth.loading">تأیید و ورود</BaseButton>
|
||||
<BaseButton
|
||||
block
|
||||
variant="outline"
|
||||
type="button"
|
||||
class="mt-2"
|
||||
:disabled="!canResend"
|
||||
@click="resend"
|
||||
>
|
||||
<template v-if="canResend">ارسال مجدد کد</template>
|
||||
<template v-else>ارسال مجدد کد ({{ timerLabel }})</template>
|
||||
</BaseButton>
|
||||
<BaseButton block variant="ghost" type="button" class="mt-2" @click="changePhone">
|
||||
تغییر شماره موبایل
|
||||
</BaseButton>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.auth-page {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.auth-page__scroll {
|
||||
padding-top: 36px;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.auth-header {
|
||||
.otp-box {
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
padding: 0;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
caret-color: var(--color-primary);
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
|
||||
.otp-box:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.otp-box--error {
|
||||
border-color: var(--color-danger);
|
||||
}
|
||||
|
||||
.input-error-text {
|
||||
color: var(--color-danger);
|
||||
font-size: 0.8125rem;
|
||||
margin: 4px 0 0;
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
.otp-box {
|
||||
width: 42px;
|
||||
height: 48px;
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,6 +6,7 @@ import BaseInput from '@/components/ui/BaseInput.vue'
|
||||
import FormErrorBanner from '@/components/ui/FormErrorBanner.vue'
|
||||
import BaseButton from '@/components/ui/BaseButton.vue'
|
||||
import { normalizePhone } from '@/utils/format'
|
||||
import { validatePhone } from '@/utils/validation'
|
||||
import {
|
||||
FORM_VALIDATION_SUMMARY,
|
||||
clearFieldError,
|
||||
@@ -40,7 +41,7 @@ async function submit() {
|
||||
if (!lastName.value.trim()) {
|
||||
errors.lastName = 'نام خانوادگی الزامی است.'
|
||||
}
|
||||
if (!/^09\d{9}$/.test(normalized)) {
|
||||
if (!validatePhone(normalized)) {
|
||||
errors.phone = 'شماره موبایل معتبر نیست.'
|
||||
}
|
||||
|
||||
@@ -64,10 +65,10 @@ async function submit() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page auth-page">
|
||||
<div class="page__scroll auth-page__scroll">
|
||||
<div class="page page-shell auth-page">
|
||||
<div class="page__scroll flex flex-col justify-center py-6">
|
||||
<div class="auth-header">
|
||||
<div class="logo">🧵</div>
|
||||
<div class="text-5xl mb-3">🧵</div>
|
||||
<h1 class="page-title">ثبتنام</h1>
|
||||
<p class="page-subtitle">ایجاد حساب کاربری جدید</p>
|
||||
</div>
|
||||
@@ -100,36 +101,12 @@ async function submit() {
|
||||
<BaseButton block :loading="auth.loading" @click="submit">دریافت کد تأیید</BaseButton>
|
||||
</div>
|
||||
|
||||
<p class="auth-footer text-center text-sm">
|
||||
قبلاً ثبتنام کردهاید؟
|
||||
<router-link to="/login">ورود</router-link>
|
||||
</p>
|
||||
<div class="mt-6 w-full max-w-[var(--form-max-width)]">
|
||||
<p class="m-0 mb-2.5 text-center text-sm text-muted">قبلاً ثبتنام کردهاید؟</p>
|
||||
<RouterLink v-slot="{ navigate }" to="/login" custom>
|
||||
<BaseButton block variant="outline" type="button" @click="navigate">ورود</BaseButton>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.auth-page {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.auth-page__scroll {
|
||||
padding-top: 36px;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.auth-header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.auth-footer {
|
||||
margin-top: 24px;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
</style>
|
||||
|
||||
+45
-35
@@ -30,49 +30,59 @@ function goToOrdering() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MobileCartPage
|
||||
class="mobile-only"
|
||||
:items="items"
|
||||
:company-name="companyName"
|
||||
:total-meterage="cartStore.totalMeterage"
|
||||
:max-meterage="cartStore.maxMeterage"
|
||||
:remaining-capacity="cartStore.remainingCapacity"
|
||||
:is-empty="isEmpty"
|
||||
@back="router.push('/')"
|
||||
@remove="removeItem"
|
||||
@checkout="proceedToCheckout"
|
||||
@select-company="router.push('/')"
|
||||
/>
|
||||
<div class="cart-view">
|
||||
<MobileCartPage
|
||||
class="mobile-only"
|
||||
:items="items"
|
||||
:company-name="companyName"
|
||||
:total-meterage="cartStore.totalMeterage"
|
||||
:max-meterage="cartStore.maxMeterage"
|
||||
:remaining-capacity="cartStore.remainingCapacity"
|
||||
:is-empty="isEmpty"
|
||||
@back="router.push('/')"
|
||||
@remove="removeItem"
|
||||
@checkout="proceedToCheckout"
|
||||
@select-company="router.push('/')"
|
||||
/>
|
||||
|
||||
<DesktopCartPage
|
||||
class="desktop-only"
|
||||
:items="items"
|
||||
:company-name="companyName"
|
||||
:total-meterage="cartStore.totalMeterage"
|
||||
:max-meterage="cartStore.maxMeterage"
|
||||
:remaining-capacity="cartStore.remainingCapacity"
|
||||
:is-empty="isEmpty"
|
||||
@remove="removeItem"
|
||||
@checkout="proceedToCheckout"
|
||||
@select-company="router.push('/')"
|
||||
@back="goToOrdering"
|
||||
/>
|
||||
<DesktopCartPage
|
||||
class="desktop-only"
|
||||
:items="items"
|
||||
:company-name="companyName"
|
||||
:total-meterage="cartStore.totalMeterage"
|
||||
:max-meterage="cartStore.maxMeterage"
|
||||
:remaining-capacity="cartStore.remainingCapacity"
|
||||
:is-empty="isEmpty"
|
||||
@remove="removeItem"
|
||||
@checkout="proceedToCheckout"
|
||||
@select-company="router.push('/')"
|
||||
@back="goToOrdering"
|
||||
/>
|
||||
|
||||
<div v-if="showFullDialog" class="dialog-overlay" @click.self="showFullDialog = false">
|
||||
<div class="dialog card">
|
||||
<h3>سقف متراژ تکمیل شد</h3>
|
||||
<p class="text-sm">
|
||||
سقف متراژ این فاکتور تکمیل شده است. آیا مایلید ادامه محصولات را در فاکتور جدید ثبت کنید؟
|
||||
</p>
|
||||
<div class="dialog-actions">
|
||||
<BaseButton variant="outline" @click="proceedAnyway">تسویه همین فاکتور</BaseButton>
|
||||
<BaseButton @click="createNewInvoice">فاکتور جدید</BaseButton>
|
||||
<div v-if="showFullDialog" class="dialog-overlay" @click.self="showFullDialog = false">
|
||||
<div class="dialog card">
|
||||
<h3>سقف متراژ تکمیل شد</h3>
|
||||
<p class="text-sm">
|
||||
سقف متراژ این فاکتور تکمیل شده است. آیا مایلید ادامه محصولات را در فاکتور جدید ثبت کنید؟
|
||||
</p>
|
||||
<div class="dialog-actions">
|
||||
<BaseButton variant="outline" @click="proceedAnyway">تسویه همین فاکتور</BaseButton>
|
||||
<BaseButton @click="createNewInvoice">فاکتور جدید</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.cart-view {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dialog h3 {
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import BaseButton from '@/components/ui/BaseButton.vue'
|
||||
import IconCart from '@/components/icons/IconCart.vue'
|
||||
import EmptyState from '@/components/ui/EmptyState.vue'
|
||||
import IconTrash from '@/components/icons/IconTrash.vue'
|
||||
import IconChevronBack from '@/components/icons/IconChevronBack.vue'
|
||||
import { formatMeterage, formatNumber } from '@/utils/format'
|
||||
@@ -82,11 +82,9 @@ const emit = defineEmits<{
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="cart-mobile-page__empty">
|
||||
<IconCart :size="56" />
|
||||
<p>سبد خرید خالی است</p>
|
||||
<EmptyState v-else variant="cart" message="برای شروع سفارش، یک شرکت انتخاب کنید">
|
||||
<BaseButton @click="emit('selectCompany')">انتخاب شرکت</BaseButton>
|
||||
</div>
|
||||
</EmptyState>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -96,8 +94,11 @@ const emit = defineEmits<{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
padding: 12px 0 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cart-mobile-page__nav {
|
||||
@@ -112,6 +113,7 @@ const emit = defineEmits<{
|
||||
font-size: 1.0625rem;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
@@ -119,6 +121,7 @@ const emit = defineEmits<{
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -156,6 +159,7 @@ const emit = defineEmits<{
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
background: var(--color-elevated);
|
||||
}
|
||||
|
||||
.cart-mobile-table__head,
|
||||
@@ -168,7 +172,7 @@ const emit = defineEmits<{
|
||||
}
|
||||
|
||||
.cart-mobile-table__head {
|
||||
background: #f1f5f9;
|
||||
background: var(--color-summary-bg);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-muted);
|
||||
@@ -176,6 +180,7 @@ const emit = defineEmits<{
|
||||
|
||||
.cart-mobile-table__row {
|
||||
border-top: 1px solid var(--color-border);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.col-product strong {
|
||||
@@ -209,23 +214,25 @@ const emit = defineEmits<{
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
color: #64748b;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.cart-mobile-page__footer {
|
||||
flex-shrink: 0;
|
||||
margin: 0 calc(-1 * var(--page-padding));
|
||||
padding: 12px var(--page-padding);
|
||||
margin: 0;
|
||||
padding: 12px 0 calc(12px + env(safe-area-inset-bottom, 0));
|
||||
border-top: 1px solid var(--color-border);
|
||||
box-shadow: 0 -4px 16px rgba(15, 23, 42, 0.06);
|
||||
background: #fff;
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
.cart-mobile-summary {
|
||||
margin-bottom: 12px;
|
||||
padding: 14px 16px;
|
||||
background: #eef2f7;
|
||||
background: var(--color-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-text);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.cart-mobile-summary__row {
|
||||
@@ -236,16 +243,6 @@ const emit = defineEmits<{
|
||||
}
|
||||
|
||||
.cart-mobile-summary__row:not(:last-child) {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.cart-mobile-page__empty {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
color: var(--color-text-muted);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useCatalogStore } from '@/stores/catalog'
|
||||
import PageHeader from '@/components/layout/PageHeader.vue'
|
||||
import SearchField from '@/components/ui/SearchField.vue'
|
||||
import EmptyState from '@/components/ui/EmptyState.vue'
|
||||
import IconCatalog from '@/components/icons/IconCatalog.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const catalogStore = useCatalogStore()
|
||||
const loading = ref(true)
|
||||
|
||||
const companyId = computed(() => route.params.companyId as string)
|
||||
const company = computed(() => catalogStore.getCompany(companyId.value))
|
||||
@@ -16,11 +18,25 @@ const search = ref('')
|
||||
|
||||
const catalogs = computed(() => catalogStore.fetchCatalogs(companyId.value, search.value))
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
if (!catalogStore.getCompany(companyId.value)) {
|
||||
await catalogStore.loadCompanies()
|
||||
}
|
||||
await catalogStore.loadCatalogs(companyId.value)
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
watch(companyId, load)
|
||||
|
||||
function openCatalog(catalogId: string) {
|
||||
router.push(`/companies/${companyId.value}/catalogs/${catalogId}/products`)
|
||||
}
|
||||
|
||||
function getProductCount(catalogId: string) {
|
||||
const catalog = catalogStore.getCatalog(catalogId)
|
||||
if (catalog?.productCount != null) return catalog.productCount
|
||||
return catalogStore.fetchProducts(catalogId).length
|
||||
}
|
||||
</script>
|
||||
@@ -42,8 +58,8 @@ function getProductCount(catalogId: string) {
|
||||
class="catalog-page__search"
|
||||
/>
|
||||
|
||||
<div class="page__scroll page__scroll--with-nav">
|
||||
<div class="catalog-mobile-list">
|
||||
<div class="page__scroll page__scroll--with-nav catalog-mobile-scroll">
|
||||
<div v-if="catalogs.length > 0" class="catalog-mobile-list">
|
||||
<button
|
||||
v-for="catalog in catalogs"
|
||||
:key="catalog.id"
|
||||
@@ -58,8 +74,12 @@ function getProductCount(catalogId: string) {
|
||||
</div>
|
||||
<span class="catalog-card__count">{{ getProductCount(catalog.id) }} محصول</span>
|
||||
</button>
|
||||
<div v-if="catalogs.length === 0" class="catalog-empty">کالیتهای یافت نشد.</div>
|
||||
</div>
|
||||
<EmptyState
|
||||
v-else
|
||||
:variant="search ? 'search' : 'catalogs'"
|
||||
:message="search ? 'با این جستجو کالیتهای پیدا نشد' : 'برای این شرکت هنوز کالیتهای ثبت نشده است'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -76,7 +96,7 @@ function getProductCount(catalogId: string) {
|
||||
class="catalog-page__search"
|
||||
/>
|
||||
|
||||
<div class="desktop-grid-3">
|
||||
<div v-if="catalogs.length > 0" class="desktop-grid-3">
|
||||
<button
|
||||
v-for="catalog in catalogs"
|
||||
:key="catalog.id"
|
||||
@@ -93,13 +113,18 @@ function getProductCount(catalogId: string) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="catalogs.length === 0" class="catalog-empty">کالیتهای یافت نشد.</div>
|
||||
<EmptyState
|
||||
v-else
|
||||
:variant="search ? 'search' : 'catalogs'"
|
||||
:message="search ? 'با این جستجو کالیتهای پیدا نشد' : 'برای این شرکت هنوز کالیتهای ثبت نشده است'"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.catalog-mobile-page {
|
||||
background: #fff;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
padding: 12px 0 0;
|
||||
}
|
||||
|
||||
@@ -107,6 +132,13 @@ function getProductCount(catalogId: string) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.catalog-mobile-scroll {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.catalog-mobile-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -121,8 +153,9 @@ function getProductCount(catalogId: string) {
|
||||
padding: 14px 16px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-surface);
|
||||
background: var(--color-elevated);
|
||||
box-shadow: var(--shadow-sm);
|
||||
color: var(--color-text);
|
||||
text-align: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -135,7 +168,7 @@ function getProductCount(catalogId: string) {
|
||||
}
|
||||
|
||||
.catalog-card:hover {
|
||||
border-color: #cbd5e1;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
@@ -157,6 +190,7 @@ function getProductCount(catalogId: string) {
|
||||
.catalog-card__info h3 {
|
||||
margin: 0 0 4px;
|
||||
font-size: 0.9375rem;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.catalog-card__info p {
|
||||
@@ -170,10 +204,4 @@ function getProductCount(catalogId: string) {
|
||||
font-weight: 600;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.catalog-empty {
|
||||
text-align: center;
|
||||
padding: 32px;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useCartStore } from '@/stores/cart'
|
||||
import { useOrderStore } from '@/stores/order'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useCatalogStore } from '@/stores/catalog'
|
||||
import PageHeader from '@/components/layout/PageHeader.vue'
|
||||
import BaseButton from '@/components/ui/BaseButton.vue'
|
||||
import IconSuccessCircle from '@/components/icons/IconSuccessCircle.vue'
|
||||
import { toPersianDigits } from '@/utils/format'
|
||||
import { getCompanyById } from '@/data/mockData'
|
||||
import type { Order } from '@/types'
|
||||
import type { BankAccount, Order } from '@/types'
|
||||
|
||||
const router = useRouter()
|
||||
const cartStore = useCartStore()
|
||||
const orderStore = useOrderStore()
|
||||
const authStore = useAuthStore()
|
||||
const catalogStore = useCatalogStore()
|
||||
const bankAccounts = ref<BankAccount[]>([])
|
||||
const submitting = ref(false)
|
||||
|
||||
const companyId = computed(() => {
|
||||
const firstItem = cartStore.activeCart?.items[0]
|
||||
@@ -23,16 +26,24 @@ const companyId = computed(() => {
|
||||
const companyName = computed(
|
||||
() => cartStore.companyNamesLabel || cartStore.activeCart?.companyName || '',
|
||||
)
|
||||
const bankAccounts = computed(() => orderStore.getBankAccountsForCompany(companyId.value))
|
||||
|
||||
onMounted(async () => {
|
||||
if (!catalogStore.getCompany(companyId.value)) {
|
||||
await catalogStore.loadCompanies()
|
||||
}
|
||||
if (companyId.value) {
|
||||
bankAccounts.value = await catalogStore.loadBankAccounts(companyId.value)
|
||||
}
|
||||
})
|
||||
|
||||
const expiryMessage = computed(() => {
|
||||
const hour = getCompanyById(companyId.value)?.cashInvoiceValidUntilHour ?? 17
|
||||
const hour = catalogStore.getCompany(companyId.value)?.cashInvoiceValidUntilHour ?? 17
|
||||
const time = toPersianDigits(String(hour).padStart(2, '0'))
|
||||
return `این فاکتور تا ساعت ${time}:۰۰ امروز معتبر است.`
|
||||
})
|
||||
|
||||
const accountsSectionTitle = computed(() => {
|
||||
const company = getCompanyById(companyId.value)
|
||||
const company = catalogStore.getCompany(companyId.value)
|
||||
const name = (company?.name || companyName.value).replace(/^شرکت\s+/u, '')
|
||||
return `شماره حسابهای شرکت ${name}`
|
||||
})
|
||||
@@ -47,21 +58,29 @@ function formatIban(value: string) {
|
||||
return clean.replace(/(.{4})/g, '$1 ').trim()
|
||||
}
|
||||
|
||||
function createOrder(): Order | null {
|
||||
if (!authStore.user || !cartStore.activeCart) return null
|
||||
const order = orderStore.submitOrder(authStore.user.id, cartStore.activeCart, 'cash')
|
||||
cartStore.clearActiveCart()
|
||||
return order
|
||||
async function createOrder(): Promise<Order | null> {
|
||||
if (!authStore.user || !cartStore.activeCart || submitting.value) return null
|
||||
submitting.value = true
|
||||
try {
|
||||
const order = await orderStore.submitOrder(authStore.user.id, cartStore.activeCart, 'cash')
|
||||
await cartStore.hydrate()
|
||||
return order
|
||||
} catch (e) {
|
||||
alert(e instanceof Error ? e.message : 'ثبت سفارش ناموفق بود.')
|
||||
return null
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function viewOrder() {
|
||||
const order = createOrder()
|
||||
async function viewOrder() {
|
||||
const order = await createOrder()
|
||||
if (!order) return
|
||||
router.replace(`/orders/${order.id}?track=1`)
|
||||
}
|
||||
|
||||
function registerReceipt() {
|
||||
const order = createOrder()
|
||||
async function registerReceipt() {
|
||||
const order = await createOrder()
|
||||
if (!order) return
|
||||
router.replace(`/orders/${order.id}?track=1&receipt=1`)
|
||||
}
|
||||
@@ -200,7 +219,8 @@ function registerReceipt() {
|
||||
.accounts-panel {
|
||||
padding: 16px;
|
||||
border-radius: 16px;
|
||||
background: #eef2f7;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.accounts-panel__title {
|
||||
@@ -214,8 +234,9 @@ function registerReceipt() {
|
||||
.bank-card {
|
||||
padding: 14px 16px;
|
||||
border-radius: 14px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.04);
|
||||
background: var(--color-elevated);
|
||||
color: var(--color-text);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.bank-card + .bank-card {
|
||||
@@ -269,7 +290,7 @@ function registerReceipt() {
|
||||
.bank-card__field + .bank-card__field {
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.bank-card__label {
|
||||
@@ -289,7 +310,6 @@ function registerReceipt() {
|
||||
padding: 12px 0 calc(12px + env(safe-area-inset-bottom, 0));
|
||||
background: var(--color-bg);
|
||||
border-top: 1px solid var(--color-border);
|
||||
box-shadow: 0 -4px 16px rgba(15, 23, 42, 0.06);
|
||||
margin: 0 calc(-1 * var(--page-padding));
|
||||
padding-left: var(--page-padding);
|
||||
padding-right: var(--page-padding);
|
||||
|
||||
@@ -281,7 +281,7 @@ function resetForm() {
|
||||
min-height: 120px;
|
||||
border: 1.5px dashed var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
background: #fafafa;
|
||||
background: var(--color-surface);
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
@@ -86,18 +86,22 @@ function toggleMenu(id: string) {
|
||||
openMenuId.value = openMenuId.value === id ? '' : id
|
||||
}
|
||||
|
||||
function submitOrder() {
|
||||
async function submitOrder() {
|
||||
if (!authStore.user || !cartStore.activeCart || checks.value.length === 0) return
|
||||
|
||||
const order = orderStore.submitOrder(
|
||||
authStore.user.id,
|
||||
cartStore.activeCart,
|
||||
'check',
|
||||
[...orderStore.draftChecks],
|
||||
orderStore.draftCheckMode ?? 'brief',
|
||||
)
|
||||
cartStore.clearActiveCart()
|
||||
router.replace(`/orders/${order.id}/success`)
|
||||
try {
|
||||
const order = await orderStore.submitOrder(
|
||||
authStore.user.id,
|
||||
cartStore.activeCart,
|
||||
'check',
|
||||
[...orderStore.draftChecks],
|
||||
orderStore.draftCheckMode ?? 'brief',
|
||||
)
|
||||
await cartStore.hydrate()
|
||||
router.replace(`/orders/${order.id}/success`)
|
||||
} catch (e) {
|
||||
alert(e instanceof Error ? e.message : 'ثبت سفارش ناموفق بود.')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -115,9 +119,10 @@ function submitOrder() {
|
||||
</header>
|
||||
|
||||
<div v-if="checks.length === 0" class="check-list-page__empty page__scroll page__scroll--with-nav">
|
||||
<p>چکی ثبت نشده است.</p>
|
||||
<BaseButton v-if="isCheckout" @click="addAnother">ثبت چک جدید</BaseButton>
|
||||
<BaseButton v-else variant="outline" @click="router.push('/')">ثبت سفارش جدید</BaseButton>
|
||||
<EmptyState variant="checks" message="برای ادامه، حداقل یک چک ثبت کنید">
|
||||
<BaseButton v-if="isCheckout" @click="addAnother">ثبت چک جدید</BaseButton>
|
||||
<BaseButton v-else variant="outline" @click="router.push('/')">ثبت سفارش جدید</BaseButton>
|
||||
</EmptyState>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
@@ -268,7 +273,7 @@ function submitOrder() {
|
||||
</div>
|
||||
|
||||
<div v-else class="desktop-checkout-shell desktop-only">
|
||||
<EmptyState message="چکی ثبت نشده است">
|
||||
<EmptyState variant="checks" message="برای ادامه، حداقل یک چک ثبت کنید">
|
||||
<BaseButton v-if="isCheckout" @click="addAnother">ثبت چک جدید</BaseButton>
|
||||
</EmptyState>
|
||||
</div>
|
||||
@@ -293,7 +298,7 @@ function submitOrder() {
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
padding: 12px var(--page-padding) 0;
|
||||
background: #ffffff;
|
||||
background: var(--color-bg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -360,8 +365,10 @@ function submitOrder() {
|
||||
.summary-card {
|
||||
padding: 14px 12px;
|
||||
border-radius: 14px;
|
||||
background: #f1f5f9;
|
||||
background: var(--color-summary-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
text-align: center;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.summary-card__label {
|
||||
@@ -422,8 +429,9 @@ function submitOrder() {
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
border-radius: 14px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #eef2f7;
|
||||
background: var(--color-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.check-card__menu-wrap {
|
||||
@@ -450,7 +458,7 @@ function submitOrder() {
|
||||
min-width: 88px;
|
||||
padding: 4px;
|
||||
border-radius: var(--radius-sm);
|
||||
background: #fff;
|
||||
background: var(--color-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
@@ -505,7 +513,7 @@ function submitOrder() {
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
border: 1px solid var(--color-border);
|
||||
background: #fff;
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
.check-card__thumb--empty {
|
||||
@@ -519,8 +527,8 @@ function submitOrder() {
|
||||
.rass-panel {
|
||||
padding: 16px;
|
||||
border-radius: 14px;
|
||||
background: #ecfdf3;
|
||||
border: 1px solid #bbf7d0;
|
||||
background: var(--color-success-light);
|
||||
border: 1px solid var(--color-success);
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -529,26 +537,26 @@ function submitOrder() {
|
||||
margin: 0 0 6px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #166534;
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.rass-panel__days {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
color: #15803d;
|
||||
color: var(--color-success);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.rass-panel__date {
|
||||
margin: 6px 0 0;
|
||||
font-size: 0.8125rem;
|
||||
color: #166534;
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.check-list-page__footer {
|
||||
flex-shrink: 0;
|
||||
padding: 12px 0 calc(12px + env(safe-area-inset-bottom, 0));
|
||||
background: #fff;
|
||||
background: var(--color-bg);
|
||||
border-top: 1px solid var(--color-border);
|
||||
box-shadow: 0 -4px 16px rgba(15, 23, 42, 0.06);
|
||||
margin: 0 calc(-1 * var(--page-padding));
|
||||
|
||||
@@ -71,8 +71,9 @@ function selectMode(mode: 'full' | 'brief') {
|
||||
padding: 32px 24px;
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 2px 12px rgba(15, 23, 42, 0.06);
|
||||
background: var(--color-elevated);
|
||||
color: var(--color-text);
|
||||
box-shadow: var(--shadow-sm);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
box-shadow 0.15s,
|
||||
|
||||
@@ -99,9 +99,10 @@ function selectType(type: 'cash' | 'check') {
|
||||
padding: 32px 24px;
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
background: #fff;
|
||||
background: var(--color-elevated);
|
||||
box-shadow: var(--shadow-sm);
|
||||
cursor: pointer;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.settlement-card--desktop {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useCatalogStore } from '@/stores/catalog'
|
||||
import { useCartStore } from '@/stores/cart'
|
||||
@@ -11,14 +12,20 @@ import type { Company } from '@/types'
|
||||
const router = useRouter()
|
||||
const catalogStore = useCatalogStore()
|
||||
const cartStore = useCartStore()
|
||||
const loading = ref(true)
|
||||
|
||||
const companies = catalogStore.getActiveCompanies()
|
||||
const companies = computed(() => catalogStore.getActiveCompanies())
|
||||
|
||||
const displayMeta: Record<string, { description: string; logo: typeof IconParLogo }> = {
|
||||
par: { description: 'تولید و عرضه انواع پارچه', logo: IconParLogo },
|
||||
'fam-baft': { description: 'تولید و عرضه انواع پارچه', logo: IconFamBaftLogo },
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await catalogStore.loadCompanies()
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
function selectCompany(companyId: string) {
|
||||
const company = catalogStore.getCompany(companyId)
|
||||
if (!company) return
|
||||
@@ -41,21 +48,26 @@ function companyLogo(slug: string) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MobileCompanySelect
|
||||
class="mobile-only"
|
||||
:companies="companies"
|
||||
:card-variant="cardVariant"
|
||||
:company-description="companyDescription"
|
||||
:company-logo="companyLogo"
|
||||
@select="selectCompany"
|
||||
/>
|
||||
<div v-if="loading" class="page page__scroll" style="padding: 2rem; text-align: center">
|
||||
در حال بارگذاری…
|
||||
</div>
|
||||
<template v-else>
|
||||
<MobileCompanySelect
|
||||
class="mobile-only"
|
||||
:companies="companies"
|
||||
:card-variant="cardVariant"
|
||||
:company-description="companyDescription"
|
||||
:company-logo="companyLogo"
|
||||
@select="selectCompany"
|
||||
/>
|
||||
|
||||
<DesktopHomeDashboard
|
||||
class="desktop-only"
|
||||
:companies="companies"
|
||||
:card-variant="cardVariant"
|
||||
:company-description="companyDescription"
|
||||
:company-logo="companyLogo"
|
||||
@select="selectCompany"
|
||||
/>
|
||||
<DesktopHomeDashboard
|
||||
class="desktop-only"
|
||||
:companies="companies"
|
||||
:card-variant="cardVariant"
|
||||
:company-description="companyDescription"
|
||||
:company-logo="companyLogo"
|
||||
@select="selectCompany"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -46,7 +46,8 @@ defineEmits<{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
background: #fff;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
@@ -91,13 +92,14 @@ defineEmits<{
|
||||
.company-brand-card--par {
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
box-shadow: 0 8px 24px rgba(29, 53, 87, 0.25);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.company-brand-card--fam-baft {
|
||||
background: #f3ece3;
|
||||
color: #1a202c;
|
||||
box-shadow: 0 4px 16px rgba(15, 23, 42, 0.06);
|
||||
background: var(--color-brand-warm);
|
||||
color: var(--color-brand-warm-text);
|
||||
box-shadow: var(--shadow-card);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.company-brand-card__logo {
|
||||
@@ -121,6 +123,6 @@ defineEmits<{
|
||||
}
|
||||
|
||||
.company-brand-card--fam-baft .company-brand-card__desc {
|
||||
color: #718096;
|
||||
color: var(--color-brand-warm-muted);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -64,8 +64,31 @@ watch(destinationAccountId, () => {
|
||||
if (!hasFieldErrors(fieldErrors.value)) formError.value = ''
|
||||
})
|
||||
|
||||
const bankAccounts = computed(() =>
|
||||
order.value ? orderStore.getBankAccountsForCompany(order.value.companyId) : [],
|
||||
const bankAccounts = ref(
|
||||
[] as Awaited<ReturnType<typeof orderStore.getBankAccountsForCompany>>,
|
||||
)
|
||||
|
||||
watch(
|
||||
() => order.value?.companyId,
|
||||
async (companyId) => {
|
||||
if (!companyId) {
|
||||
bankAccounts.value = []
|
||||
return
|
||||
}
|
||||
bankAccounts.value = await orderStore.getBankAccountsForCompany(companyId)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
() => orderId.value,
|
||||
async (id) => {
|
||||
if (!id) return
|
||||
if (!orderStore.getOrderById(id)) {
|
||||
await orderStore.fetchOrder(id)
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const statusHero = computed(() => (order.value ? getStatusHero(order.value.status) : null))
|
||||
@@ -114,7 +137,7 @@ async function handleReceiptImage(event: Event) {
|
||||
receiptImage.value = await fileToBase64(file)
|
||||
}
|
||||
|
||||
function submitReceipt() {
|
||||
async function submitReceipt() {
|
||||
if (!order.value) return
|
||||
|
||||
fieldErrors.value = {}
|
||||
@@ -142,17 +165,21 @@ function submitReceipt() {
|
||||
return
|
||||
}
|
||||
|
||||
orderStore.submitCashReceipt(order.value.id, {
|
||||
amount,
|
||||
transferDate: receiptDate.value,
|
||||
transferTime: receiptTime.value || '12:00',
|
||||
trackingNumber: trackingNumber.value,
|
||||
destinationAccountId: destinationAccountId.value,
|
||||
receiptImage: receiptImage.value || undefined,
|
||||
})
|
||||
showReceiptForm.value = false
|
||||
fieldErrors.value = {}
|
||||
formError.value = ''
|
||||
try {
|
||||
await orderStore.submitCashReceipt(order.value.id, {
|
||||
amount,
|
||||
transferDate: receiptDate.value,
|
||||
transferTime: receiptTime.value || '12:00',
|
||||
trackingNumber: trackingNumber.value,
|
||||
destinationAccountId: destinationAccountId.value,
|
||||
receiptImage: receiptImage.value || undefined,
|
||||
})
|
||||
showReceiptForm.value = false
|
||||
fieldErrors.value = {}
|
||||
formError.value = ''
|
||||
} catch (e) {
|
||||
formError.value = e instanceof Error ? e.message : 'ثبت رسید ناموفق بود.'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -388,7 +415,8 @@ function submitReceipt() {
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
padding: 12px var(--page-padding) 0;
|
||||
background: #ffffff;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -457,15 +485,15 @@ function submitReceipt() {
|
||||
}
|
||||
|
||||
.order-info-card {
|
||||
background: #ffffff;
|
||||
background: var(--color-elevated);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 4px 20px rgba(15, 23, 42, 0.08);
|
||||
box-shadow: var(--shadow-sm);
|
||||
padding: 20px 18px 8px;
|
||||
}
|
||||
|
||||
.order-info-card__head {
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
@@ -490,7 +518,7 @@ function submitReceipt() {
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px 0;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.order-info-card__row--last {
|
||||
@@ -511,7 +539,7 @@ function submitReceipt() {
|
||||
}
|
||||
|
||||
.order-info-card__value--accent {
|
||||
color: #2b66e3;
|
||||
color: var(--color-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@@ -525,7 +553,7 @@ function submitReceipt() {
|
||||
.order-detail-page__footer {
|
||||
flex-shrink: 0;
|
||||
padding: 12px 0;
|
||||
background: #ffffff;
|
||||
background: var(--color-bg);
|
||||
border-top: 1px solid var(--color-border);
|
||||
box-shadow: 0 -4px 16px rgba(15, 23, 42, 0.06);
|
||||
margin: 0 calc(-1 * var(--page-padding));
|
||||
@@ -550,7 +578,7 @@ function submitReceipt() {
|
||||
padding: 16px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 16px;
|
||||
background: #ffffff;
|
||||
background: var(--color-elevated);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ const emit = defineEmits<{
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EmptyState v-else message="سفارشی یافت نشد">
|
||||
<EmptyState v-else variant="orders" message="هنوز سفارشی ثبت نکردهاید">
|
||||
<BaseButton @click="emit('newOrder')">ثبت سفارش جدید</BaseButton>
|
||||
</EmptyState>
|
||||
</div>
|
||||
|
||||
@@ -4,15 +4,21 @@ import BaseButton from '@/components/ui/BaseButton.vue'
|
||||
import FilterTabs from '@/components/ui/FilterTabs.vue'
|
||||
import StatusBadge from '@/components/ui/StatusBadge.vue'
|
||||
import IconChevronBack from '@/components/icons/IconChevronBack.vue'
|
||||
import IconChevronLeft from '@/components/icons/IconChevronLeft.vue'
|
||||
import IconParLogo from '@/components/icons/IconParLogo.vue'
|
||||
import IconFamBaftLogo from '@/components/icons/IconFamBaftLogo.vue'
|
||||
import { formatCurrency } from '@/utils/format'
|
||||
import { formatCurrency, formatNumber, toPersianDigits } from '@/utils/format'
|
||||
import type { Order } from '@/types'
|
||||
|
||||
defineProps<{
|
||||
tab: string
|
||||
tabs: { id: string; label: string }[]
|
||||
orders: Order[]
|
||||
totalCount: number
|
||||
currentPage: number
|
||||
totalPages: number
|
||||
paginationRange: { start: number; end: number }
|
||||
visiblePages: (number | 'ellipsis')[]
|
||||
companySlug: (order: Order) => string
|
||||
iconVariant: (order: Order) => string
|
||||
}>()
|
||||
@@ -21,6 +27,7 @@ const emit = defineEmits<{
|
||||
'update:tab': [value: string]
|
||||
select: [id: string]
|
||||
back: []
|
||||
'set-page': [page: number]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -44,7 +51,7 @@ const emit = defineEmits<{
|
||||
@update:model-value="emit('update:tab', $event)"
|
||||
/>
|
||||
|
||||
<div class="page__scroll page__scroll--with-nav">
|
||||
<div class="page__scroll page__scroll--with-nav orders-mobile-page__scroll">
|
||||
<div v-if="orders.length > 0" class="orders-mobile-page__list">
|
||||
<button
|
||||
v-for="order in orders"
|
||||
@@ -69,16 +76,57 @@ const emit = defineEmits<{
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<EmptyState v-else message="سفارشی یافت نشد">
|
||||
<EmptyState v-else variant="orders" message="هنوز سفارشی ثبت نکردهاید">
|
||||
<BaseButton @click="emit('back')">ثبت سفارش جدید</BaseButton>
|
||||
</EmptyState>
|
||||
|
||||
<div v-if="totalCount > 0" class="orders-mobile-pagination">
|
||||
<p class="orders-mobile-pagination__summary">
|
||||
{{ toPersianDigits(String(paginationRange.start)) }}–
|
||||
{{ toPersianDigits(String(paginationRange.end)) }}
|
||||
از {{ formatNumber(totalCount) }}
|
||||
</p>
|
||||
<div class="orders-mobile-pagination__controls">
|
||||
<button
|
||||
type="button"
|
||||
class="orders-mobile-pagination__arrow"
|
||||
:disabled="currentPage <= 1"
|
||||
aria-label="صفحه قبل"
|
||||
@click="emit('set-page', currentPage - 1)"
|
||||
>
|
||||
<IconChevronLeft :size="16" class="orders-mobile-pagination__arrow--next" />
|
||||
</button>
|
||||
<template v-for="(page, index) in visiblePages" :key="`${page}-${index}`">
|
||||
<span v-if="page === 'ellipsis'" class="orders-mobile-pagination__ellipsis">…</span>
|
||||
<button
|
||||
v-else
|
||||
type="button"
|
||||
class="orders-mobile-pagination__page"
|
||||
:class="{ 'orders-mobile-pagination__page--active': page === currentPage }"
|
||||
@click="emit('set-page', page)"
|
||||
>
|
||||
{{ toPersianDigits(String(page)) }}
|
||||
</button>
|
||||
</template>
|
||||
<button
|
||||
type="button"
|
||||
class="orders-mobile-pagination__arrow"
|
||||
:disabled="currentPage >= totalPages"
|
||||
aria-label="صفحه بعد"
|
||||
@click="emit('set-page', currentPage + 1)"
|
||||
>
|
||||
<IconChevronLeft :size="16" class="orders-mobile-pagination__arrow--prev" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.orders-mobile-page {
|
||||
background: #ffffff;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
padding: 12px 0 0;
|
||||
}
|
||||
|
||||
@@ -87,6 +135,7 @@ const emit = defineEmits<{
|
||||
grid-template-columns: 40px 1fr 40px;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding-inline: var(--page-padding);
|
||||
}
|
||||
|
||||
.orders-mobile-page__title {
|
||||
@@ -94,6 +143,7 @@ const emit = defineEmits<{
|
||||
font-size: 1.0625rem;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
@@ -101,6 +151,7 @@ const emit = defineEmits<{
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -118,6 +169,12 @@ const emit = defineEmits<{
|
||||
|
||||
.orders-mobile-page__tabs {
|
||||
margin-bottom: 16px;
|
||||
padding-inline: var(--page-padding);
|
||||
}
|
||||
|
||||
.orders-mobile-page__scroll {
|
||||
padding-inline: var(--page-padding);
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.orders-mobile-page__list {
|
||||
@@ -130,10 +187,11 @@ const emit = defineEmits<{
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
border: none;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 16px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 12px rgba(15, 23, 42, 0.06);
|
||||
background: var(--color-elevated);
|
||||
box-shadow: var(--shadow-card);
|
||||
color: var(--color-text);
|
||||
text-align: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -154,18 +212,21 @@ const emit = defineEmits<{
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 6px;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.order-card__company {
|
||||
margin: 0 0 6px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.order-card__amount {
|
||||
margin: 0 0 10px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.order-card__badge {
|
||||
@@ -189,11 +250,73 @@ const emit = defineEmits<{
|
||||
}
|
||||
|
||||
.order-card__icon--fam-baft {
|
||||
background: #f3ece3;
|
||||
background: var(--color-brand-warm);
|
||||
color: var(--color-brand-warm-text);
|
||||
}
|
||||
|
||||
.order-card__icon--success {
|
||||
background: var(--color-success-soft);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.orders-mobile-pagination {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.orders-mobile-pagination__summary {
|
||||
margin: 0;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.orders-mobile-pagination__controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.orders-mobile-pagination__arrow,
|
||||
.orders-mobile-pagination__page {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-elevated);
|
||||
color: var(--color-text);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
font-family: inherit;
|
||||
font-size: 0.8125rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.orders-mobile-pagination__arrow:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.orders-mobile-pagination__arrow--prev {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.orders-mobile-pagination__page--active {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.orders-mobile-pagination__ellipsis {
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import BaseButton from '@/components/ui/BaseButton.vue'
|
||||
import EmptyState from '@/components/ui/EmptyState.vue'
|
||||
import IconCart from '@/components/icons/IconCart.vue'
|
||||
import { formatMeterage, formatNumber } from '@/utils/format'
|
||||
import type { Product } from '@/types'
|
||||
@@ -61,7 +62,7 @@ const emit = defineEmits<{
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="product-desktop-grid">
|
||||
<div v-if="products.length > 0" class="product-desktop-grid">
|
||||
<article v-for="product in products" :key="product.id" class="product-desktop-card">
|
||||
<div class="product-desktop-card__preview" aria-hidden="true" />
|
||||
<div class="product-desktop-card__body">
|
||||
@@ -113,6 +114,12 @@ const emit = defineEmits<{
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<EmptyState
|
||||
v-else
|
||||
variant="products"
|
||||
message="برای این کالیته یا رنگ، محصولی ثبت نشده است"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<aside v-if="itemCount > 0" class="desktop-summary-panel">
|
||||
@@ -288,10 +295,12 @@ const emit = defineEmits<{
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--color-text);
|
||||
color: #fff;
|
||||
color: var(--color-bg);
|
||||
padding: 10px 20px;
|
||||
border-radius: var(--radius-sm);
|
||||
z-index: 150;
|
||||
box-shadow: var(--shadow-md);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.desktop-summary-panel__action :deep(.btn) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import PageHeader from '@/components/layout/PageHeader.vue'
|
||||
import CartMeterageFooter from '@/components/ui/CartMeterageFooter.vue'
|
||||
import BaseButton from '@/components/ui/BaseButton.vue'
|
||||
import EmptyState from '@/components/ui/EmptyState.vue'
|
||||
import { formatMeterage } from '@/utils/format'
|
||||
import type { Product } from '@/types'
|
||||
|
||||
@@ -53,7 +54,7 @@ const emit = defineEmits<{
|
||||
</div>
|
||||
|
||||
<div class="page__scroll product-mobile-page__scroll">
|
||||
<div class="product-mobile-list">
|
||||
<div v-if="products.length > 0" class="product-mobile-list">
|
||||
<article v-for="product in products" :key="product.id" class="product-mobile-card">
|
||||
<div class="product-mobile-card__head">
|
||||
<h3>{{ product.name }}</h3>
|
||||
@@ -97,6 +98,12 @@ const emit = defineEmits<{
|
||||
<p v-if="errors[product.id]" class="error-text">{{ errors[product.id] }}</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<EmptyState
|
||||
v-else
|
||||
variant="products"
|
||||
message="برای این کالیته یا رنگ، محصولی ثبت نشده است"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="toast" class="toast">{{ toast }}</div>
|
||||
@@ -113,12 +120,17 @@ const emit = defineEmits<{
|
||||
|
||||
<style scoped>
|
||||
.product-mobile-page {
|
||||
background: #fff;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
padding: 12px 0 0;
|
||||
}
|
||||
|
||||
.product-mobile-page__scroll {
|
||||
padding-bottom: 76px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.color-tabs {
|
||||
@@ -219,9 +231,13 @@ const emit = defineEmits<{
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--color-text);
|
||||
color: #fff;
|
||||
color: var(--color-bg);
|
||||
padding: 10px 20px;
|
||||
border-radius: var(--radius-sm);
|
||||
z-index: 150;
|
||||
box-shadow: var(--shadow-md);
|
||||
font-size: 0.875rem;
|
||||
white-space: nowrap;
|
||||
max-width: calc(100% - 32px);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,51 +1,83 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useTimeoutFn } from '@vueuse/core'
|
||||
import BaseButton from '@/components/ui/BaseButton.vue'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import ProfileSubpageLayout from '@/views/profile/components/ProfileSubpageLayout.vue'
|
||||
import { useThemeStore, type ThemePreference } from '@/stores/theme'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const language = ref('fa')
|
||||
const currency = ref('irr')
|
||||
const density = ref('comfortable')
|
||||
const theme = useThemeStore()
|
||||
const saved = ref(false)
|
||||
|
||||
const { start: hideSaved } = useTimeoutFn(() => {
|
||||
saved.value = false
|
||||
}, 2000)
|
||||
|
||||
const themeOptions: {
|
||||
value: ThemePreference
|
||||
label: string
|
||||
description: string
|
||||
icon: string
|
||||
}[] = [
|
||||
{
|
||||
value: 'light',
|
||||
label: 'روشن',
|
||||
description: 'پسزمینه روشن و کنتراست بالا',
|
||||
icon: 'vuesax-linear:sun-1',
|
||||
},
|
||||
{
|
||||
value: 'dark',
|
||||
label: 'تاریک',
|
||||
description: 'مناسب استفاده در شب',
|
||||
icon: 'vuesax-linear:moon',
|
||||
},
|
||||
{
|
||||
value: 'system',
|
||||
label: 'سیستم',
|
||||
description: 'همتراز با تنظیمات دستگاه',
|
||||
icon: 'vuesax-linear:mobile',
|
||||
},
|
||||
]
|
||||
|
||||
function save() {
|
||||
saved.value = true
|
||||
setTimeout(() => {
|
||||
saved.value = false
|
||||
}, 2000)
|
||||
hideSaved()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProfileSubpageLayout title="تنظیمات نمایش" subtitle="زبان، واحد پول و حالت نمایش">
|
||||
<div class="display-form">
|
||||
<label class="display-field">
|
||||
<span class="display-field__label">زبان</span>
|
||||
<select v-model="language" class="display-field__control">
|
||||
<option value="fa">فارسی</option>
|
||||
<option value="en">English</option>
|
||||
</select>
|
||||
</label>
|
||||
<ProfileSubpageLayout title="تنظیمات نمایش" subtitle="حالت نمایش روشن، تاریک یا سیستم">
|
||||
<div class="display-page">
|
||||
<fieldset class="display-fieldset">
|
||||
<legend class="display-fieldset__legend">حالت نمایش</legend>
|
||||
|
||||
<label class="display-field">
|
||||
<span class="display-field__label">واحد پول</span>
|
||||
<select v-model="currency" class="display-field__control">
|
||||
<option value="irr">تومان</option>
|
||||
<option value="rial">ریال</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<fieldset class="display-field">
|
||||
<legend class="display-field__label">حالت نمایش</legend>
|
||||
<div class="display-options">
|
||||
<label class="display-option">
|
||||
<input v-model="density" type="radio" value="comfortable" />
|
||||
<span>راحت</span>
|
||||
</label>
|
||||
<label class="display-option">
|
||||
<input v-model="density" type="radio" value="compact" />
|
||||
<span>فشرده</span>
|
||||
</label>
|
||||
<div class="display-grid" role="radiogroup" aria-label="حالت نمایش">
|
||||
<button
|
||||
v-for="option in themeOptions"
|
||||
:key="option.value"
|
||||
type="button"
|
||||
class="theme-card"
|
||||
:class="{ 'theme-card--active': theme.preference === option.value }"
|
||||
role="radio"
|
||||
:aria-checked="theme.preference === option.value"
|
||||
@click="theme.preference = option.value"
|
||||
>
|
||||
<span class="theme-card__icon" aria-hidden="true">
|
||||
<BasicIcon :icon="option.icon" :size="26" :stroke-width="1.5" />
|
||||
</span>
|
||||
<span class="theme-card__body">
|
||||
<strong class="theme-card__label">{{ option.label }}</strong>
|
||||
<span class="theme-card__desc">{{ option.description }}</span>
|
||||
</span>
|
||||
<span class="theme-card__check" aria-hidden="true">
|
||||
<BasicIcon
|
||||
v-if="theme.preference === option.value"
|
||||
icon="vuesax-linear:tick-circle"
|
||||
:size="20"
|
||||
:stroke-width="1.75"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
@@ -56,71 +88,113 @@ function save() {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.display-form {
|
||||
.display-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.display-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
.display-fieldset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.display-field__label {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.display-field__control {
|
||||
width: 100%;
|
||||
min-height: var(--btn-height);
|
||||
padding: 0 14px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
font-family: inherit;
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.display-field__control:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px var(--color-primary-soft);
|
||||
}
|
||||
|
||||
.display-options {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.display-option {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 40px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
.display-fieldset__legend {
|
||||
margin: 0 0 12px;
|
||||
padding: 0;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.display-option:has(input:checked) {
|
||||
.display-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.theme-card {
|
||||
display: grid;
|
||||
grid-template-columns: 48px 1fr 24px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 16px;
|
||||
background: var(--color-elevated);
|
||||
color: var(--color-text);
|
||||
text-align: right;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
background 0.15s,
|
||||
box-shadow 0.15s,
|
||||
transform 0.15s;
|
||||
}
|
||||
|
||||
.theme-card:hover {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.theme-card:active {
|
||||
transform: scale(0.99);
|
||||
}
|
||||
|
||||
.theme-card--active {
|
||||
border-color: var(--color-primary);
|
||||
background: var(--color-primary-soft);
|
||||
color: var(--color-primary);
|
||||
font-weight: 600;
|
||||
box-shadow: 0 0 0 1px var(--color-primary);
|
||||
}
|
||||
|
||||
.display-option input {
|
||||
accent-color: var(--color-primary);
|
||||
.theme-card__icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text-muted);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.theme-card--active .theme-card__icon {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.theme-card__body {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.theme-card__label {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.theme-card__desc {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-text-muted);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.theme-card--active .theme-card__label {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.theme-card__check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-primary);
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
.display-saved {
|
||||
|
||||
@@ -28,7 +28,7 @@ function clearField(key: string) {
|
||||
if (!hasFieldErrors(fieldErrors.value)) formError.value = ''
|
||||
}
|
||||
|
||||
function save() {
|
||||
async function save() {
|
||||
fieldErrors.value = {}
|
||||
formError.value = ''
|
||||
saved.value = false
|
||||
@@ -46,11 +46,15 @@ function save() {
|
||||
return
|
||||
}
|
||||
|
||||
auth.updateProfile({
|
||||
const updated = await auth.updateProfile({
|
||||
firstName: firstName.value.trim(),
|
||||
lastName: lastName.value.trim(),
|
||||
nationalId: nationalId.value || undefined,
|
||||
})
|
||||
if (!updated) {
|
||||
formError.value = 'ذخیره پروفایل ناموفق بود.'
|
||||
return
|
||||
}
|
||||
saved.value = true
|
||||
setTimeout(() => {
|
||||
saved.value = false
|
||||
|
||||
@@ -118,7 +118,7 @@ const settingsItems: Array<{
|
||||
{
|
||||
id: 'display',
|
||||
label: 'تنظیمات نمایش',
|
||||
description: 'زبان، واحد پول و حالت نمایش',
|
||||
description: 'تم روشن، تاریک یا سیستم',
|
||||
icon: IconLayoutGrid,
|
||||
path: '/profile/display',
|
||||
},
|
||||
|
||||
@@ -7,17 +7,12 @@ import IconChevronLeft from '@/components/icons/IconChevronLeft.vue'
|
||||
import IconProfile from '@/components/icons/IconProfile.vue'
|
||||
import IconCheckBook from '@/components/icons/IconCheckBook.vue'
|
||||
import IconOrders from '@/components/icons/IconOrders.vue'
|
||||
import IconSettings from '@/components/icons/IconSettings.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
const orderStore = useOrderStore()
|
||||
|
||||
const initials = computed(() => {
|
||||
const first = auth.user?.firstName?.[0] ?? ''
|
||||
const last = auth.user?.lastName?.[0] ?? ''
|
||||
return `${first}${last}` || '؟'
|
||||
})
|
||||
|
||||
const draftCheckCount = computed(() => orderStore.draftChecks.length)
|
||||
|
||||
const menuItems = computed(() => [
|
||||
@@ -28,6 +23,13 @@ const menuItems = computed(() => [
|
||||
path: '/profile/edit',
|
||||
icon: IconProfile,
|
||||
},
|
||||
{
|
||||
id: 'display',
|
||||
label: 'تنظیمات نمایش',
|
||||
description: 'تم روشن، تاریک یا سیستم',
|
||||
path: '/profile/display',
|
||||
icon: IconSettings,
|
||||
},
|
||||
{
|
||||
id: 'checks',
|
||||
label: 'لیست چکها',
|
||||
@@ -51,7 +53,9 @@ const emit = defineEmits<{ logout: [] }>()
|
||||
<template>
|
||||
<div class="page profile-page">
|
||||
<div class="profile-hero">
|
||||
<div class="profile-hero__avatar">{{ initials }}</div>
|
||||
<div class="profile-hero__avatar" aria-hidden="true">
|
||||
<IconProfile :size="36" :stroke-width="1.5" />
|
||||
</div>
|
||||
<h1 class="profile-hero__name">{{ auth.fullName }}</h1>
|
||||
<p class="profile-hero__phone" dir="ltr">{{ auth.user?.phone }}</p>
|
||||
</div>
|
||||
@@ -108,8 +112,7 @@ const emit = defineEmits<{ logout: [] }>()
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.profile-hero__name {
|
||||
@@ -143,12 +146,13 @@ const emit = defineEmits<{ logout: [] }>()
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
border: none;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 16px;
|
||||
background: var(--color-surface);
|
||||
background: var(--color-elevated);
|
||||
box-shadow: var(--shadow-sm);
|
||||
cursor: pointer;
|
||||
text-align: right;
|
||||
color: var(--color-text);
|
||||
transition: box-shadow 0.15s;
|
||||
}
|
||||
|
||||
@@ -201,15 +205,14 @@ const emit = defineEmits<{ logout: [] }>()
|
||||
|
||||
.profile-menu__chevron {
|
||||
color: var(--color-text-muted);
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.profile-logout {
|
||||
width: 100%;
|
||||
height: var(--btn-height);
|
||||
border: 1px solid #fecaca;
|
||||
border: 1px solid var(--color-danger-light);
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
background: var(--color-elevated);
|
||||
color: var(--color-danger);
|
||||
font-family: inherit;
|
||||
font-size: 0.9375rem;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user