@crm-market/template-shared 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/css/custom.css +70 -0
- package/assets/css/remixicon.css +2782 -0
- package/assets/css/satoshi-font.css +31 -0
- package/assets/fonts/flaticon.css +463 -0
- package/assets/fonts/flaticon.eot +0 -0
- package/assets/fonts/flaticon.html +2153 -0
- package/assets/fonts/flaticon.svg +441 -0
- package/assets/fonts/flaticon.ttf +0 -0
- package/assets/fonts/flaticon.woff +0 -0
- package/assets/fonts/flaticon.woff2 +0 -0
- package/assets/fonts/remixicon.eot +0 -0
- package/assets/fonts/remixicon.svg +8230 -0
- package/assets/fonts/remixicon.ttf +0 -0
- package/assets/fonts/remixicon.woff +0 -0
- package/assets/fonts/remixicon.woff2 +0 -0
- package/assets/scss/_variables.scss +31 -0
- package/assets/scss/components/_about.scss +58 -0
- package/assets/scss/components/_authentication.scss +124 -0
- package/assets/scss/components/_backtoptop.scss +27 -0
- package/assets/scss/components/_banner.scss +396 -0
- package/assets/scss/components/_best-deals.scss +74 -0
- package/assets/scss/components/_blank.scss +40 -0
- package/assets/scss/components/_blog.scss +193 -0
- package/assets/scss/components/_cart.scss +108 -0
- package/assets/scss/components/_categories.scss +82 -0
- package/assets/scss/components/_checkout.scss +110 -0
- package/assets/scss/components/_dashboard.scss +388 -0
- package/assets/scss/components/_faq.scss +22 -0
- package/assets/scss/components/_filter-rang.scss +109 -0
- package/assets/scss/components/_footer.scss +270 -0
- package/assets/scss/components/_global.scss +550 -0
- package/assets/scss/components/_header.scss +587 -0
- package/assets/scss/components/_hurry.scss +52 -0
- package/assets/scss/components/_navbar.scss +1008 -0
- package/assets/scss/components/_offers.scss +689 -0
- package/assets/scss/components/_pagination.scss +71 -0
- package/assets/scss/components/_popup.scss +172 -0
- package/assets/scss/components/_preloader.scss +108 -0
- package/assets/scss/components/_products.scss +1147 -0
- package/assets/scss/components/_rtl.scss +806 -0
- package/assets/scss/components/_services.scss +16 -0
- package/assets/scss/components/_sidebar.scss +259 -0
- package/assets/scss/style.css +6676 -0
- package/assets/scss/style.css.map +1 -0
- package/assets/scss/style.scss +40 -0
- package/assets/webfonts/Satoshi-Bold.eot +0 -0
- package/assets/webfonts/Satoshi-Bold.woff +0 -0
- package/assets/webfonts/Satoshi-Bold.woff2 +0 -0
- package/assets/webfonts/Satoshi-Medium.eot +0 -0
- package/assets/webfonts/Satoshi-Medium.woff +0 -0
- package/assets/webfonts/Satoshi-Medium.woff2 +0 -0
- package/assets/webfonts/Satoshi-Regular.eot +0 -0
- package/assets/webfonts/Satoshi-Regular.woff +0 -0
- package/assets/webfonts/Satoshi-Regular.woff2 +0 -0
- package/components/AboutUs/AboutUsTuan.vue +25 -0
- package/components/AboutUs/Statistics.vue +42 -0
- package/components/AboutUs/SubscribeToTheNewsletter.vue +57 -0
- package/components/AddAddress/index.vue +70 -0
- package/components/BestSellers/Products.vue +1562 -0
- package/components/BestSellers/RecentlyViewed.vue +304 -0
- package/components/Cart/ProductQuantity.vue +29 -0
- package/components/Cart/index.vue +167 -0
- package/components/Categories/index.vue +305 -0
- package/components/ChangePassword/index.vue +71 -0
- package/components/Checkout/index.vue +192 -0
- package/components/Common/DashboardNavigation.vue +37 -0
- package/components/Common/PageBanner.vue +28 -0
- package/components/Common/ProductCard.vue +152 -0
- package/components/Common/Services.vue +58 -0
- package/components/Contact/ContactForm.vue +91 -0
- package/components/Contact/ContactInfo.vue +74 -0
- package/components/Dashboard/RecentOrder.vue +173 -0
- package/components/Dashboard/index.vue +79 -0
- package/components/EditAddress/index.vue +119 -0
- package/components/EditProfile/index.vue +97 -0
- package/components/FAQ/index.vue +121 -0
- package/components/FeaturedProduct/FeaturedProducts.vue +304 -0
- package/components/FeaturedProduct/Products.vue +1562 -0
- package/components/ForgotPassword/index.vue +51 -0
- package/components/Layout/BackToUp.vue +38 -0
- package/components/Layout/Copyright.vue +25 -0
- package/components/Layout/Footer.vue +183 -0
- package/components/Layout/FooterTwo.vue +165 -0
- package/components/Layout/LocationOption.vue +178 -0
- package/components/Layout/MiddleHeader.vue +229 -0
- package/components/Layout/MiddleHeaderThree.vue +204 -0
- package/components/Layout/MiddleHeaderTwo.vue +240 -0
- package/components/Layout/Navbar.vue +185 -0
- package/components/Layout/NavbarStyleFour.vue +334 -0
- package/components/Layout/NavbarStyleThree.vue +188 -0
- package/components/Layout/NavbarStyleTwo.vue +108 -0
- package/components/Layout/Preloader.vue +18 -0
- package/components/Layout/RTLSwitchBtn.vue +40 -0
- package/components/Layout/ResponsiveNavbar.vue +431 -0
- package/components/Layout/TopHeader.vue +130 -0
- package/components/Login/index.vue +94 -0
- package/components/MyAccount/index.vue +154 -0
- package/components/NewArrivals/Products.vue +1969 -0
- package/components/NewArrivals/RecentlyViewed.vue +304 -0
- package/components/OrderDetails/index.vue +77 -0
- package/components/OrderHistory/index.vue +197 -0
- package/components/PrivacyPolicy/index.vue +112 -0
- package/components/ProductDetails/ProductDetailsTab.vue +343 -0
- package/components/ProductDetails/ProductQuantity.vue +29 -0
- package/components/ProductDetails/RecentlyViewed.vue +304 -0
- package/components/ProductDetails/index.vue +268 -0
- package/components/Products/RecentlyViewed.vue +304 -0
- package/components/Products/index.vue +292 -0
- package/components/Register/index.vue +88 -0
- package/components/TermsConditions/index.vue +112 -0
- package/components/TrendingProducts/FeaturedProducts.vue +304 -0
- package/components/TrendingProducts/Products.vue +1564 -0
- package/components/Wishlist/ProductQuantity.vue +29 -0
- package/components/Wishlist/index.vue +128 -0
- package/composables/useCart.ts +149 -0
- package/composables/useCategories.ts +87 -0
- package/composables/useCheckout.ts +131 -0
- package/composables/useProducts.ts +162 -0
- package/composables/useSiteConfig.ts +236 -0
- package/composables/useTemplateSections.ts +74 -0
- package/e2e/cart.spec.ts +71 -0
- package/e2e/fixtures/mock-api.ts +166 -0
- package/e2e/homepage.spec.ts +65 -0
- package/e2e/layout.spec.ts +73 -0
- package/e2e/navigation.spec.ts +61 -0
- package/e2e/pages/cart.page.ts +44 -0
- package/e2e/pages/homepage.page.ts +46 -0
- package/e2e/playwright.config.ts +32 -0
- package/e2e/products.spec.ts +33 -0
- package/layouts/default.vue +94 -0
- package/layouts/inner.vue +70 -0
- package/nuxt.config.ts +86 -0
- package/package.json +38 -0
- package/pages/about-us.vue +12 -0
- package/pages/address.vue +10 -0
- package/pages/cart.vue +10 -0
- package/pages/categories.vue +10 -0
- package/pages/change-password.vue +10 -0
- package/pages/checkout.vue +10 -0
- package/pages/contact.vue +11 -0
- package/pages/dashboard.vue +10 -0
- package/pages/edit-address.vue +10 -0
- package/pages/edit-profile.vue +10 -0
- package/pages/faq.vue +10 -0
- package/pages/forgot-password.vue +10 -0
- package/pages/login.vue +10 -0
- package/pages/my-account.vue +10 -0
- package/pages/order-details.vue +10 -0
- package/pages/order-history.vue +10 -0
- package/pages/privacy-policy.vue +10 -0
- package/pages/product-details.vue +10 -0
- package/pages/products.vue +10 -0
- package/pages/register.vue +10 -0
- package/pages/terms-conditions.vue +10 -0
- package/pages/wishlist.vue +10 -0
- package/plugins/site-init.client.ts +24 -0
- package/plugins/vuetify.ts +18 -0
- package/types/index.ts +121 -0
- package/utils/image.ts +13 -0
- package/utils/store.ts +21 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="middle-header-area ptb-20">
|
|
3
|
+
<div class="container">
|
|
4
|
+
<div class="d-lg-flex justify-content-between align-items-center">
|
|
5
|
+
<div
|
|
6
|
+
class="d-flex align-items-center middle-header-left justify-content-center justify-content-lg-end"
|
|
7
|
+
>
|
|
8
|
+
<NuxtLink to="/" class="d-none d-lg-block middle-logo">
|
|
9
|
+
<img :src="logoUrl || defaultLogo" alt="logo" />
|
|
10
|
+
</NuxtLink>
|
|
11
|
+
<div class="d-flex">
|
|
12
|
+
<div class="dropdown">
|
|
13
|
+
<button
|
|
14
|
+
class="dropdown-toggle border border-2 bg-transparent all-categories"
|
|
15
|
+
@click="toggleAllCategories"
|
|
16
|
+
:aria-pressed="show ? 'true' : 'false'"
|
|
17
|
+
:class="{ show: buttonShowState }"
|
|
18
|
+
>
|
|
19
|
+
All Categories
|
|
20
|
+
</button>
|
|
21
|
+
<ul class="dropdown-menu" :class="{ toggler: show }">
|
|
22
|
+
<li v-for="category in topLevelCategories" :key="category.id">
|
|
23
|
+
<NuxtLink
|
|
24
|
+
class="dropdown-item"
|
|
25
|
+
:to="`/products?categoryId=${category.id}`"
|
|
26
|
+
>
|
|
27
|
+
{{ category.name }}
|
|
28
|
+
</NuxtLink>
|
|
29
|
+
</li>
|
|
30
|
+
<li v-if="topLevelCategories.length === 0">
|
|
31
|
+
<span class="dropdown-item text-muted">No categories</span>
|
|
32
|
+
</li>
|
|
33
|
+
</ul>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="position-relative">
|
|
36
|
+
<input
|
|
37
|
+
type="text"
|
|
38
|
+
class="form-control border-2"
|
|
39
|
+
placeholder="Search for products..."
|
|
40
|
+
v-model="searchQuery"
|
|
41
|
+
@keyup.enter="handleSearch"
|
|
42
|
+
/>
|
|
43
|
+
<button
|
|
44
|
+
type="submit"
|
|
45
|
+
class="position-absolute top-50 end-0 translate-middle-y btn btn-primary fs-14 fw-medium text-white"
|
|
46
|
+
@click="handleSearch"
|
|
47
|
+
>
|
|
48
|
+
<i class="ri-search-line"></i>
|
|
49
|
+
<span class="d-none d-sm-inline-block">Search</span>
|
|
50
|
+
</button>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<ul
|
|
56
|
+
class="d-flex middle-header-right ps-0 mb-0 list-unstyled justify-content-center justify-content-lg-end mt-3 mt-lg-0"
|
|
57
|
+
>
|
|
58
|
+
<li class="buy-info">
|
|
59
|
+
<NuxtLink to="/login" class="text-decoration-none">
|
|
60
|
+
<div class="d-flex align-items-center">
|
|
61
|
+
<div class="flex-shrink-0">
|
|
62
|
+
<i class="ri-user-line"></i>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="flex-grow-1 ms-10">
|
|
65
|
+
<span>Login Your</span>
|
|
66
|
+
<h3>Account</h3>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</NuxtLink>
|
|
70
|
+
</li>
|
|
71
|
+
<li class="buy-info">
|
|
72
|
+
<NuxtLink to="/wishlist" class="text-decoration-none">
|
|
73
|
+
<div class="position-relative me-9">
|
|
74
|
+
<i class="ri-heart-line"></i>
|
|
75
|
+
<span class="count">0</span>
|
|
76
|
+
</div>
|
|
77
|
+
</NuxtLink>
|
|
78
|
+
</li>
|
|
79
|
+
<li class="buy-info">
|
|
80
|
+
<div class="dropdown">
|
|
81
|
+
<a
|
|
82
|
+
class="text-decoration-none"
|
|
83
|
+
href="javascript:void(0);"
|
|
84
|
+
@click="toggleBuyInfo"
|
|
85
|
+
:aria-pressed="active ? 'true' : 'false'"
|
|
86
|
+
:class="{ active: buttonActiveState }"
|
|
87
|
+
>
|
|
88
|
+
<div class="d-flex align-items-center">
|
|
89
|
+
<div class="flex-shrink-0">
|
|
90
|
+
<div class="position-relative me-9">
|
|
91
|
+
<i class="ri-shopping-cart-line"></i>
|
|
92
|
+
<span class="count">{{ itemCount }}</span>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="flex-grow-1 ms-1">
|
|
96
|
+
<span>Your Cart</span>
|
|
97
|
+
<h3>${{ subtotal.toFixed(2) }}</h3>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</a>
|
|
101
|
+
<ul
|
|
102
|
+
class="dropdown-menu cart-dropdown dropdown-menu-end"
|
|
103
|
+
:class="{ toggler: active }"
|
|
104
|
+
>
|
|
105
|
+
<li
|
|
106
|
+
v-for="item in cart.items.slice(0, 3)"
|
|
107
|
+
:key="item.productId"
|
|
108
|
+
class="d-flex justify-content-between align-items-center cart-item"
|
|
109
|
+
>
|
|
110
|
+
<button
|
|
111
|
+
class="p-0 border-0 bg-transparent delete-btn"
|
|
112
|
+
@click="removeFromCart(item.productId)"
|
|
113
|
+
>
|
|
114
|
+
<i class="ri-delete-bin-7-line"></i>
|
|
115
|
+
</button>
|
|
116
|
+
<NuxtLink
|
|
117
|
+
class="dropdown-item d-flex align-items-center"
|
|
118
|
+
:to="`/product-details?id=${item.productId}`"
|
|
119
|
+
>
|
|
120
|
+
<img
|
|
121
|
+
v-if="item.image"
|
|
122
|
+
:src="resolveImageUrl(item.image)"
|
|
123
|
+
alt="product"
|
|
124
|
+
/>
|
|
125
|
+
<div class="ms-3 d-inline-block text-truncate">
|
|
126
|
+
<span>{{ item.name }}</span>
|
|
127
|
+
<h3>${{ (item.discountPrice || item.price).toFixed(2) }}</h3>
|
|
128
|
+
</div>
|
|
129
|
+
</NuxtLink>
|
|
130
|
+
</li>
|
|
131
|
+
<li v-if="isEmpty" class="cart-item text-center text-muted py-3">
|
|
132
|
+
Cart is empty
|
|
133
|
+
</li>
|
|
134
|
+
<template v-if="!isEmpty">
|
|
135
|
+
<li
|
|
136
|
+
class="d-flex justify-content-between align-items-center cart-item"
|
|
137
|
+
>
|
|
138
|
+
<h3 class="fs-15 mb-0">Total</h3>
|
|
139
|
+
<h3 class="fs-15 mb-0">${{ subtotal.toFixed(2) }}</h3>
|
|
140
|
+
</li>
|
|
141
|
+
<li
|
|
142
|
+
class="d-flex justify-content-between align-items-center cart-item"
|
|
143
|
+
>
|
|
144
|
+
<NuxtLink to="/cart" class="btn btn-primary fs-14 text-white">
|
|
145
|
+
View Cart
|
|
146
|
+
</NuxtLink>
|
|
147
|
+
<NuxtLink
|
|
148
|
+
to="/checkout"
|
|
149
|
+
class="btn btn-warning fs-14 text-white"
|
|
150
|
+
>
|
|
151
|
+
Checkout
|
|
152
|
+
</NuxtLink>
|
|
153
|
+
</li>
|
|
154
|
+
</template>
|
|
155
|
+
</ul>
|
|
156
|
+
</div>
|
|
157
|
+
</li>
|
|
158
|
+
</ul>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
</template>
|
|
163
|
+
|
|
164
|
+
<script lang="ts">
|
|
165
|
+
import { defineComponent, ref } from "vue";
|
|
166
|
+
|
|
167
|
+
import defaultLogoImg from "~/assets/images/logo.png";
|
|
168
|
+
|
|
169
|
+
export default defineComponent({
|
|
170
|
+
name: "MiddleHeader",
|
|
171
|
+
setup() {
|
|
172
|
+
const show = ref(false);
|
|
173
|
+
const buttonShowState = ref(false);
|
|
174
|
+
const active = ref(false);
|
|
175
|
+
const buttonActiveState = ref(false);
|
|
176
|
+
const searchQuery = ref("");
|
|
177
|
+
const defaultLogo = defaultLogoImg;
|
|
178
|
+
const router = useRouter();
|
|
179
|
+
|
|
180
|
+
const { topLevelCategories } = useCategories();
|
|
181
|
+
const { logoUrl } = useSiteConfig();
|
|
182
|
+
const { cart, itemCount, subtotal, isEmpty, removeFromCart } = useCart();
|
|
183
|
+
|
|
184
|
+
const resolveImageUrl = (url: string) => {
|
|
185
|
+
if (!url) return '';
|
|
186
|
+
if (url.startsWith('http')) return url;
|
|
187
|
+
const config = useRuntimeConfig();
|
|
188
|
+
const apiBase = config.public.apiBase || 'http://localhost:3001/api';
|
|
189
|
+
const baseUrl = apiBase.replace('/api', '');
|
|
190
|
+
return `${baseUrl}${url}`;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const toggleAllCategories = () => {
|
|
194
|
+
show.value = !show.value;
|
|
195
|
+
buttonShowState.value = !buttonShowState.value;
|
|
196
|
+
};
|
|
197
|
+
const toggleBuyInfo = () => {
|
|
198
|
+
active.value = !active.value;
|
|
199
|
+
buttonActiveState.value = !buttonActiveState.value;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const handleSearch = () => {
|
|
203
|
+
if (searchQuery.value.trim()) {
|
|
204
|
+
router.push(`/products?search=${encodeURIComponent(searchQuery.value.trim())}`);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
show,
|
|
210
|
+
buttonShowState,
|
|
211
|
+
active,
|
|
212
|
+
buttonActiveState,
|
|
213
|
+
toggleAllCategories,
|
|
214
|
+
toggleBuyInfo,
|
|
215
|
+
searchQuery,
|
|
216
|
+
handleSearch,
|
|
217
|
+
defaultLogo,
|
|
218
|
+
topLevelCategories,
|
|
219
|
+
logoUrl,
|
|
220
|
+
cart,
|
|
221
|
+
itemCount,
|
|
222
|
+
subtotal,
|
|
223
|
+
isEmpty,
|
|
224
|
+
removeFromCart,
|
|
225
|
+
resolveImageUrl,
|
|
226
|
+
};
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
</script>
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="middle-header-area border-bottom ptb-20">
|
|
3
|
+
<div class="container">
|
|
4
|
+
<div class="d-lg-flex justify-content-between align-items-center">
|
|
5
|
+
<div
|
|
6
|
+
class="d-flex align-items-center middle-header-left justify-content-center justify-content-lg-end"
|
|
7
|
+
>
|
|
8
|
+
<NuxtLink to="/" class="d-none d-lg-block middle-logo style-two">
|
|
9
|
+
<img :src="logoUrl || defaultLogo" alt="logo" />
|
|
10
|
+
</NuxtLink>
|
|
11
|
+
<div class="position-relative search-style-two">
|
|
12
|
+
<input
|
|
13
|
+
type="text"
|
|
14
|
+
class="form-control border-2"
|
|
15
|
+
placeholder="Search for products..."
|
|
16
|
+
v-model="searchQuery"
|
|
17
|
+
@keyup.enter="handleSearch"
|
|
18
|
+
/>
|
|
19
|
+
<button
|
|
20
|
+
type="submit"
|
|
21
|
+
class="position-absolute top-50 end-0 translate-middle-y btn btn-success fs-14 fw-medium text-white"
|
|
22
|
+
@click="handleSearch"
|
|
23
|
+
>
|
|
24
|
+
<i class="ri-search-line"></i>
|
|
25
|
+
<span class="d-none d-sm-inline-block">Search</span>
|
|
26
|
+
</button>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<ul
|
|
31
|
+
class="d-flex middle-header-right ps-0 mb-0 list-unstyled justify-content-center justify-content-lg-end mt-3 mt-lg-0"
|
|
32
|
+
>
|
|
33
|
+
<li class="info" v-if="phone">
|
|
34
|
+
<a :href="`tel:${phone}`" class="text-decoration-none">
|
|
35
|
+
<div class="d-flex align-items-center">
|
|
36
|
+
<div class="flex-shrink-0">
|
|
37
|
+
<i class="ri-phone-line bg-danger-10"></i>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="flex-grow-1 ms-10">
|
|
40
|
+
<span>Hotline</span>
|
|
41
|
+
<h3>{{ phone }}</h3>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</a>
|
|
45
|
+
</li>
|
|
46
|
+
<li class="buy-info position-relative tops-3">
|
|
47
|
+
<NuxtLink to="/login" class="text-decoration-none success-hover-bg">
|
|
48
|
+
<div class="d-flex align-items-center">
|
|
49
|
+
<div class="flex-shrink-0">
|
|
50
|
+
<i class="ri-user-line"></i>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</NuxtLink>
|
|
54
|
+
</li>
|
|
55
|
+
<li class="buy-info tops-4 position-relative">
|
|
56
|
+
<NuxtLink
|
|
57
|
+
to="/wishlist"
|
|
58
|
+
class="text-decoration-none success-hover-bg"
|
|
59
|
+
>
|
|
60
|
+
<div class="position-relative me-9">
|
|
61
|
+
<i class="ri-heart-line"></i>
|
|
62
|
+
<span class="count bg-success">0</span>
|
|
63
|
+
</div>
|
|
64
|
+
</NuxtLink>
|
|
65
|
+
</li>
|
|
66
|
+
<li class="buy-info">
|
|
67
|
+
<div class="dropdown">
|
|
68
|
+
<a
|
|
69
|
+
class="text-decoration-none success-hover-bg"
|
|
70
|
+
href="javascript:void(0);"
|
|
71
|
+
@click="toggleBuyInfo"
|
|
72
|
+
:aria-pressed="active ? 'true' : 'false'"
|
|
73
|
+
:class="{ active: buttonActiveState }"
|
|
74
|
+
>
|
|
75
|
+
<div class="d-flex align-items-center">
|
|
76
|
+
<div class="flex-shrink-0">
|
|
77
|
+
<div class="position-relative me-9">
|
|
78
|
+
<i class="ri-shopping-cart-line"></i>
|
|
79
|
+
<span class="count bg-success">{{ itemCount }}</span>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</a>
|
|
84
|
+
<ul
|
|
85
|
+
class="dropdown-menu cart-dropdown dropdown-menu-end"
|
|
86
|
+
:class="{ toggler: active }"
|
|
87
|
+
>
|
|
88
|
+
<li
|
|
89
|
+
v-for="item in cart.items.slice(0, 3)"
|
|
90
|
+
:key="item.productId"
|
|
91
|
+
class="d-flex justify-content-between align-items-center cart-item"
|
|
92
|
+
>
|
|
93
|
+
<button
|
|
94
|
+
class="p-0 border-0 bg-transparent delete-btn"
|
|
95
|
+
@click="removeFromCart(item.productId)"
|
|
96
|
+
>
|
|
97
|
+
<i class="ri-delete-bin-7-line"></i>
|
|
98
|
+
</button>
|
|
99
|
+
<NuxtLink
|
|
100
|
+
class="dropdown-item d-flex align-items-center"
|
|
101
|
+
:to="`/product-details?id=${item.productId}`"
|
|
102
|
+
>
|
|
103
|
+
<img
|
|
104
|
+
v-if="item.image"
|
|
105
|
+
:src="resolveImageUrl(item.image)"
|
|
106
|
+
alt="product"
|
|
107
|
+
/>
|
|
108
|
+
<div class="ms-3 d-inline-block text-truncate">
|
|
109
|
+
<span>{{ item.name }}</span>
|
|
110
|
+
<h3>${{ (item.discountPrice || item.price).toFixed(2) }}</h3>
|
|
111
|
+
</div>
|
|
112
|
+
</NuxtLink>
|
|
113
|
+
</li>
|
|
114
|
+
<li v-if="isEmpty" class="cart-item text-center text-muted py-3">
|
|
115
|
+
Cart is empty
|
|
116
|
+
</li>
|
|
117
|
+
<template v-if="!isEmpty">
|
|
118
|
+
<li
|
|
119
|
+
class="d-flex justify-content-between align-items-center cart-item"
|
|
120
|
+
>
|
|
121
|
+
<h3 class="fs-15 mb-0">Total</h3>
|
|
122
|
+
<h3 class="fs-15 mb-0">${{ subtotal.toFixed(2) }}</h3>
|
|
123
|
+
</li>
|
|
124
|
+
<li
|
|
125
|
+
class="d-flex justify-content-between align-items-center cart-item"
|
|
126
|
+
>
|
|
127
|
+
<NuxtLink to="/cart" class="btn btn-primary fs-14 text-white">
|
|
128
|
+
View Cart
|
|
129
|
+
</NuxtLink>
|
|
130
|
+
<NuxtLink
|
|
131
|
+
to="/checkout"
|
|
132
|
+
class="btn btn-warning fs-14 text-white"
|
|
133
|
+
>
|
|
134
|
+
Checkout
|
|
135
|
+
</NuxtLink>
|
|
136
|
+
</li>
|
|
137
|
+
</template>
|
|
138
|
+
</ul>
|
|
139
|
+
</div>
|
|
140
|
+
</li>
|
|
141
|
+
</ul>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</template>
|
|
146
|
+
|
|
147
|
+
<script lang="ts">
|
|
148
|
+
import { defineComponent, ref, computed } from "vue";
|
|
149
|
+
|
|
150
|
+
import defaultLogoImg from "~/assets/images/grocery-shop-logo.png";
|
|
151
|
+
|
|
152
|
+
export default defineComponent({
|
|
153
|
+
name: "MiddleHeaderThree",
|
|
154
|
+
setup() {
|
|
155
|
+
const active = ref(false);
|
|
156
|
+
const buttonActiveState = ref(false);
|
|
157
|
+
const searchQuery = ref("");
|
|
158
|
+
const defaultLogo = defaultLogoImg;
|
|
159
|
+
const router = useRouter();
|
|
160
|
+
|
|
161
|
+
const { logoUrl, contactInfo } = useSiteConfig();
|
|
162
|
+
const { cart, itemCount, subtotal, isEmpty, removeFromCart } = useCart();
|
|
163
|
+
|
|
164
|
+
const phone = computed(() => contactInfo.value?.phone);
|
|
165
|
+
|
|
166
|
+
const resolveImageUrl = (url: string) => {
|
|
167
|
+
if (!url) return '';
|
|
168
|
+
if (url.startsWith('http')) return url;
|
|
169
|
+
const config = useRuntimeConfig();
|
|
170
|
+
const apiBase = config.public.apiBase || 'http://localhost:3001/api';
|
|
171
|
+
const baseUrl = apiBase.replace('/api', '');
|
|
172
|
+
return `${baseUrl}${url}`;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const toggleBuyInfo = () => {
|
|
176
|
+
active.value = !active.value;
|
|
177
|
+
buttonActiveState.value = !buttonActiveState.value;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const handleSearch = () => {
|
|
181
|
+
if (searchQuery.value.trim()) {
|
|
182
|
+
router.push(`/products?search=${encodeURIComponent(searchQuery.value.trim())}`);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
active,
|
|
188
|
+
buttonActiveState,
|
|
189
|
+
toggleBuyInfo,
|
|
190
|
+
searchQuery,
|
|
191
|
+
handleSearch,
|
|
192
|
+
defaultLogo,
|
|
193
|
+
logoUrl,
|
|
194
|
+
phone,
|
|
195
|
+
cart,
|
|
196
|
+
itemCount,
|
|
197
|
+
subtotal,
|
|
198
|
+
isEmpty,
|
|
199
|
+
removeFromCart,
|
|
200
|
+
resolveImageUrl,
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
</script>
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="middle-header-area ptb-20">
|
|
3
|
+
<div class="container">
|
|
4
|
+
<div class="d-lg-flex justify-content-between align-items-center">
|
|
5
|
+
<div
|
|
6
|
+
class="d-flex align-items-center middle-header-left justify-content-center justify-content-lg-end"
|
|
7
|
+
>
|
|
8
|
+
<NuxtLink to="/" class="d-none d-lg-block middle-logo">
|
|
9
|
+
<img :src="logoUrl || defaultLogo" alt="logo" />
|
|
10
|
+
</NuxtLink>
|
|
11
|
+
<div class="d-flex">
|
|
12
|
+
<div class="dropdown">
|
|
13
|
+
<button
|
|
14
|
+
class="dropdown-toggle border border-2 bg-transparent all-categories"
|
|
15
|
+
@click="toggleAllCategories"
|
|
16
|
+
:aria-pressed="show ? 'true' : 'false'"
|
|
17
|
+
:class="{ show: buttonShowState }"
|
|
18
|
+
>
|
|
19
|
+
All Categories
|
|
20
|
+
</button>
|
|
21
|
+
<ul class="dropdown-menu" :class="{ toggler: show }">
|
|
22
|
+
<li v-for="category in topLevelCategories" :key="category.id">
|
|
23
|
+
<NuxtLink
|
|
24
|
+
class="dropdown-item"
|
|
25
|
+
:to="`/products?categoryId=${category.id}`"
|
|
26
|
+
>
|
|
27
|
+
{{ category.name }}
|
|
28
|
+
</NuxtLink>
|
|
29
|
+
</li>
|
|
30
|
+
<li v-if="topLevelCategories.length === 0">
|
|
31
|
+
<span class="dropdown-item text-muted">No categories</span>
|
|
32
|
+
</li>
|
|
33
|
+
</ul>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="position-relative">
|
|
36
|
+
<input
|
|
37
|
+
type="text"
|
|
38
|
+
class="form-control border-2"
|
|
39
|
+
placeholder="Search for products..."
|
|
40
|
+
v-model="searchQuery"
|
|
41
|
+
@keyup.enter="handleSearch"
|
|
42
|
+
/>
|
|
43
|
+
<button
|
|
44
|
+
type="submit"
|
|
45
|
+
class="position-absolute top-50 end-0 translate-middle-y btn btn-warning fs-14 fw-medium text-white"
|
|
46
|
+
@click="handleSearch"
|
|
47
|
+
>
|
|
48
|
+
<i class="ri-search-line"></i>
|
|
49
|
+
<span class="d-none d-sm-inline-block">Search</span>
|
|
50
|
+
</button>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<ul
|
|
56
|
+
class="d-flex middle-header-right ps-0 mb-0 list-unstyled justify-content-center justify-content-lg-end mt-3 mt-lg-0"
|
|
57
|
+
>
|
|
58
|
+
<li class="info" v-if="phone">
|
|
59
|
+
<a :href="`tel:${phone}`" class="text-decoration-none">
|
|
60
|
+
<div class="d-flex align-items-center">
|
|
61
|
+
<div class="flex-shrink-0">
|
|
62
|
+
<i class="ri-phone-line bg-danger-10"></i>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="flex-grow-1 ms-10">
|
|
65
|
+
<span>Hotline</span>
|
|
66
|
+
<h3>{{ phone }}</h3>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</a>
|
|
70
|
+
</li>
|
|
71
|
+
<li class="buy-info position-relative tops-3">
|
|
72
|
+
<NuxtLink to="/login" class="text-decoration-none success-hover-bg">
|
|
73
|
+
<div class="d-flex align-items-center">
|
|
74
|
+
<div class="flex-shrink-0">
|
|
75
|
+
<i class="ri-user-line"></i>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</NuxtLink>
|
|
79
|
+
</li>
|
|
80
|
+
<li class="buy-info tops-4 position-relative">
|
|
81
|
+
<NuxtLink
|
|
82
|
+
to="/wishlist"
|
|
83
|
+
class="text-decoration-none success-hover-bg"
|
|
84
|
+
>
|
|
85
|
+
<div class="position-relative me-9">
|
|
86
|
+
<i class="ri-heart-line"></i>
|
|
87
|
+
<span class="count bg-success">0</span>
|
|
88
|
+
</div>
|
|
89
|
+
</NuxtLink>
|
|
90
|
+
</li>
|
|
91
|
+
<li class="buy-info">
|
|
92
|
+
<div class="dropdown">
|
|
93
|
+
<a
|
|
94
|
+
class="text-decoration-none success-hover-bg"
|
|
95
|
+
href="javascript:void(0);"
|
|
96
|
+
@click="toggleBuyInfo"
|
|
97
|
+
:aria-pressed="active ? 'true' : 'false'"
|
|
98
|
+
:class="{ active: buttonActiveState }"
|
|
99
|
+
>
|
|
100
|
+
<div class="d-flex align-items-center">
|
|
101
|
+
<div class="flex-shrink-0">
|
|
102
|
+
<div class="position-relative me-9">
|
|
103
|
+
<i class="ri-shopping-cart-line"></i>
|
|
104
|
+
<span class="count bg-success">{{ itemCount }}</span>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</a>
|
|
109
|
+
<ul
|
|
110
|
+
class="dropdown-menu cart-dropdown dropdown-menu-end"
|
|
111
|
+
:class="{ toggler: active }"
|
|
112
|
+
>
|
|
113
|
+
<li
|
|
114
|
+
v-for="item in cart.items.slice(0, 3)"
|
|
115
|
+
:key="item.productId"
|
|
116
|
+
class="d-flex justify-content-between align-items-center cart-item"
|
|
117
|
+
>
|
|
118
|
+
<button
|
|
119
|
+
class="p-0 border-0 bg-transparent delete-btn"
|
|
120
|
+
@click="removeFromCart(item.productId)"
|
|
121
|
+
>
|
|
122
|
+
<i class="ri-delete-bin-7-line"></i>
|
|
123
|
+
</button>
|
|
124
|
+
<NuxtLink
|
|
125
|
+
class="dropdown-item d-flex align-items-center"
|
|
126
|
+
:to="`/product-details?id=${item.productId}`"
|
|
127
|
+
>
|
|
128
|
+
<img
|
|
129
|
+
v-if="item.image"
|
|
130
|
+
:src="resolveImageUrl(item.image)"
|
|
131
|
+
alt="product"
|
|
132
|
+
/>
|
|
133
|
+
<div class="ms-3 d-inline-block text-truncate">
|
|
134
|
+
<span>{{ item.name }}</span>
|
|
135
|
+
<h3>${{ (item.discountPrice || item.price).toFixed(2) }}</h3>
|
|
136
|
+
</div>
|
|
137
|
+
</NuxtLink>
|
|
138
|
+
</li>
|
|
139
|
+
<li v-if="isEmpty" class="cart-item text-center text-muted py-3">
|
|
140
|
+
Cart is empty
|
|
141
|
+
</li>
|
|
142
|
+
<template v-if="!isEmpty">
|
|
143
|
+
<li
|
|
144
|
+
class="d-flex justify-content-between align-items-center cart-item"
|
|
145
|
+
>
|
|
146
|
+
<h3 class="fs-15 mb-0">Total</h3>
|
|
147
|
+
<h3 class="fs-15 mb-0">${{ subtotal.toFixed(2) }}</h3>
|
|
148
|
+
</li>
|
|
149
|
+
<li
|
|
150
|
+
class="d-flex justify-content-between align-items-center cart-item"
|
|
151
|
+
>
|
|
152
|
+
<NuxtLink to="/cart" class="btn btn-primary fs-14 text-white">
|
|
153
|
+
View Cart
|
|
154
|
+
</NuxtLink>
|
|
155
|
+
<NuxtLink
|
|
156
|
+
to="/checkout"
|
|
157
|
+
class="btn btn-warning fs-14 text-white"
|
|
158
|
+
>
|
|
159
|
+
Checkout
|
|
160
|
+
</NuxtLink>
|
|
161
|
+
</li>
|
|
162
|
+
</template>
|
|
163
|
+
</ul>
|
|
164
|
+
</div>
|
|
165
|
+
</li>
|
|
166
|
+
</ul>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</template>
|
|
171
|
+
|
|
172
|
+
<script lang="ts">
|
|
173
|
+
import { defineComponent, ref, computed } from "vue";
|
|
174
|
+
|
|
175
|
+
import defaultLogoImg from "~/assets/images/grocery-shop-logo.png";
|
|
176
|
+
|
|
177
|
+
export default defineComponent({
|
|
178
|
+
name: "MiddleHeaderTwo",
|
|
179
|
+
setup() {
|
|
180
|
+
const show = ref(false);
|
|
181
|
+
const buttonShowState = ref(false);
|
|
182
|
+
const active = ref(false);
|
|
183
|
+
const buttonActiveState = ref(false);
|
|
184
|
+
const searchQuery = ref("");
|
|
185
|
+
const defaultLogo = defaultLogoImg;
|
|
186
|
+
const router = useRouter();
|
|
187
|
+
|
|
188
|
+
const { topLevelCategories } = useCategories();
|
|
189
|
+
const { logoUrl, contactInfo } = useSiteConfig();
|
|
190
|
+
const { cart, itemCount, subtotal, isEmpty, removeFromCart } = useCart();
|
|
191
|
+
|
|
192
|
+
const phone = computed(() => contactInfo.value?.phone);
|
|
193
|
+
|
|
194
|
+
const resolveImageUrl = (url: string) => {
|
|
195
|
+
if (!url) return '';
|
|
196
|
+
if (url.startsWith('http')) return url;
|
|
197
|
+
const config = useRuntimeConfig();
|
|
198
|
+
const apiBase = config.public.apiBase || 'http://localhost:3001/api';
|
|
199
|
+
const baseUrl = apiBase.replace('/api', '');
|
|
200
|
+
return `${baseUrl}${url}`;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const toggleAllCategories = () => {
|
|
204
|
+
show.value = !show.value;
|
|
205
|
+
buttonShowState.value = !buttonShowState.value;
|
|
206
|
+
};
|
|
207
|
+
const toggleBuyInfo = () => {
|
|
208
|
+
active.value = !active.value;
|
|
209
|
+
buttonActiveState.value = !buttonActiveState.value;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const handleSearch = () => {
|
|
213
|
+
if (searchQuery.value.trim()) {
|
|
214
|
+
router.push(`/products?search=${encodeURIComponent(searchQuery.value.trim())}`);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
show,
|
|
220
|
+
buttonShowState,
|
|
221
|
+
active,
|
|
222
|
+
buttonActiveState,
|
|
223
|
+
toggleAllCategories,
|
|
224
|
+
toggleBuyInfo,
|
|
225
|
+
searchQuery,
|
|
226
|
+
handleSearch,
|
|
227
|
+
defaultLogo,
|
|
228
|
+
topLevelCategories,
|
|
229
|
+
logoUrl,
|
|
230
|
+
phone,
|
|
231
|
+
cart,
|
|
232
|
+
itemCount,
|
|
233
|
+
subtotal,
|
|
234
|
+
isEmpty,
|
|
235
|
+
removeFromCart,
|
|
236
|
+
resolveImageUrl,
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
</script>
|