@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.
Files changed (160) hide show
  1. package/assets/css/custom.css +70 -0
  2. package/assets/css/remixicon.css +2782 -0
  3. package/assets/css/satoshi-font.css +31 -0
  4. package/assets/fonts/flaticon.css +463 -0
  5. package/assets/fonts/flaticon.eot +0 -0
  6. package/assets/fonts/flaticon.html +2153 -0
  7. package/assets/fonts/flaticon.svg +441 -0
  8. package/assets/fonts/flaticon.ttf +0 -0
  9. package/assets/fonts/flaticon.woff +0 -0
  10. package/assets/fonts/flaticon.woff2 +0 -0
  11. package/assets/fonts/remixicon.eot +0 -0
  12. package/assets/fonts/remixicon.svg +8230 -0
  13. package/assets/fonts/remixicon.ttf +0 -0
  14. package/assets/fonts/remixicon.woff +0 -0
  15. package/assets/fonts/remixicon.woff2 +0 -0
  16. package/assets/scss/_variables.scss +31 -0
  17. package/assets/scss/components/_about.scss +58 -0
  18. package/assets/scss/components/_authentication.scss +124 -0
  19. package/assets/scss/components/_backtoptop.scss +27 -0
  20. package/assets/scss/components/_banner.scss +396 -0
  21. package/assets/scss/components/_best-deals.scss +74 -0
  22. package/assets/scss/components/_blank.scss +40 -0
  23. package/assets/scss/components/_blog.scss +193 -0
  24. package/assets/scss/components/_cart.scss +108 -0
  25. package/assets/scss/components/_categories.scss +82 -0
  26. package/assets/scss/components/_checkout.scss +110 -0
  27. package/assets/scss/components/_dashboard.scss +388 -0
  28. package/assets/scss/components/_faq.scss +22 -0
  29. package/assets/scss/components/_filter-rang.scss +109 -0
  30. package/assets/scss/components/_footer.scss +270 -0
  31. package/assets/scss/components/_global.scss +550 -0
  32. package/assets/scss/components/_header.scss +587 -0
  33. package/assets/scss/components/_hurry.scss +52 -0
  34. package/assets/scss/components/_navbar.scss +1008 -0
  35. package/assets/scss/components/_offers.scss +689 -0
  36. package/assets/scss/components/_pagination.scss +71 -0
  37. package/assets/scss/components/_popup.scss +172 -0
  38. package/assets/scss/components/_preloader.scss +108 -0
  39. package/assets/scss/components/_products.scss +1147 -0
  40. package/assets/scss/components/_rtl.scss +806 -0
  41. package/assets/scss/components/_services.scss +16 -0
  42. package/assets/scss/components/_sidebar.scss +259 -0
  43. package/assets/scss/style.css +6676 -0
  44. package/assets/scss/style.css.map +1 -0
  45. package/assets/scss/style.scss +40 -0
  46. package/assets/webfonts/Satoshi-Bold.eot +0 -0
  47. package/assets/webfonts/Satoshi-Bold.woff +0 -0
  48. package/assets/webfonts/Satoshi-Bold.woff2 +0 -0
  49. package/assets/webfonts/Satoshi-Medium.eot +0 -0
  50. package/assets/webfonts/Satoshi-Medium.woff +0 -0
  51. package/assets/webfonts/Satoshi-Medium.woff2 +0 -0
  52. package/assets/webfonts/Satoshi-Regular.eot +0 -0
  53. package/assets/webfonts/Satoshi-Regular.woff +0 -0
  54. package/assets/webfonts/Satoshi-Regular.woff2 +0 -0
  55. package/components/AboutUs/AboutUsTuan.vue +25 -0
  56. package/components/AboutUs/Statistics.vue +42 -0
  57. package/components/AboutUs/SubscribeToTheNewsletter.vue +57 -0
  58. package/components/AddAddress/index.vue +70 -0
  59. package/components/BestSellers/Products.vue +1562 -0
  60. package/components/BestSellers/RecentlyViewed.vue +304 -0
  61. package/components/Cart/ProductQuantity.vue +29 -0
  62. package/components/Cart/index.vue +167 -0
  63. package/components/Categories/index.vue +305 -0
  64. package/components/ChangePassword/index.vue +71 -0
  65. package/components/Checkout/index.vue +192 -0
  66. package/components/Common/DashboardNavigation.vue +37 -0
  67. package/components/Common/PageBanner.vue +28 -0
  68. package/components/Common/ProductCard.vue +152 -0
  69. package/components/Common/Services.vue +58 -0
  70. package/components/Contact/ContactForm.vue +91 -0
  71. package/components/Contact/ContactInfo.vue +74 -0
  72. package/components/Dashboard/RecentOrder.vue +173 -0
  73. package/components/Dashboard/index.vue +79 -0
  74. package/components/EditAddress/index.vue +119 -0
  75. package/components/EditProfile/index.vue +97 -0
  76. package/components/FAQ/index.vue +121 -0
  77. package/components/FeaturedProduct/FeaturedProducts.vue +304 -0
  78. package/components/FeaturedProduct/Products.vue +1562 -0
  79. package/components/ForgotPassword/index.vue +51 -0
  80. package/components/Layout/BackToUp.vue +38 -0
  81. package/components/Layout/Copyright.vue +25 -0
  82. package/components/Layout/Footer.vue +183 -0
  83. package/components/Layout/FooterTwo.vue +165 -0
  84. package/components/Layout/LocationOption.vue +178 -0
  85. package/components/Layout/MiddleHeader.vue +229 -0
  86. package/components/Layout/MiddleHeaderThree.vue +204 -0
  87. package/components/Layout/MiddleHeaderTwo.vue +240 -0
  88. package/components/Layout/Navbar.vue +185 -0
  89. package/components/Layout/NavbarStyleFour.vue +334 -0
  90. package/components/Layout/NavbarStyleThree.vue +188 -0
  91. package/components/Layout/NavbarStyleTwo.vue +108 -0
  92. package/components/Layout/Preloader.vue +18 -0
  93. package/components/Layout/RTLSwitchBtn.vue +40 -0
  94. package/components/Layout/ResponsiveNavbar.vue +431 -0
  95. package/components/Layout/TopHeader.vue +130 -0
  96. package/components/Login/index.vue +94 -0
  97. package/components/MyAccount/index.vue +154 -0
  98. package/components/NewArrivals/Products.vue +1969 -0
  99. package/components/NewArrivals/RecentlyViewed.vue +304 -0
  100. package/components/OrderDetails/index.vue +77 -0
  101. package/components/OrderHistory/index.vue +197 -0
  102. package/components/PrivacyPolicy/index.vue +112 -0
  103. package/components/ProductDetails/ProductDetailsTab.vue +343 -0
  104. package/components/ProductDetails/ProductQuantity.vue +29 -0
  105. package/components/ProductDetails/RecentlyViewed.vue +304 -0
  106. package/components/ProductDetails/index.vue +268 -0
  107. package/components/Products/RecentlyViewed.vue +304 -0
  108. package/components/Products/index.vue +292 -0
  109. package/components/Register/index.vue +88 -0
  110. package/components/TermsConditions/index.vue +112 -0
  111. package/components/TrendingProducts/FeaturedProducts.vue +304 -0
  112. package/components/TrendingProducts/Products.vue +1564 -0
  113. package/components/Wishlist/ProductQuantity.vue +29 -0
  114. package/components/Wishlist/index.vue +128 -0
  115. package/composables/useCart.ts +149 -0
  116. package/composables/useCategories.ts +87 -0
  117. package/composables/useCheckout.ts +131 -0
  118. package/composables/useProducts.ts +162 -0
  119. package/composables/useSiteConfig.ts +236 -0
  120. package/composables/useTemplateSections.ts +74 -0
  121. package/e2e/cart.spec.ts +71 -0
  122. package/e2e/fixtures/mock-api.ts +166 -0
  123. package/e2e/homepage.spec.ts +65 -0
  124. package/e2e/layout.spec.ts +73 -0
  125. package/e2e/navigation.spec.ts +61 -0
  126. package/e2e/pages/cart.page.ts +44 -0
  127. package/e2e/pages/homepage.page.ts +46 -0
  128. package/e2e/playwright.config.ts +32 -0
  129. package/e2e/products.spec.ts +33 -0
  130. package/layouts/default.vue +94 -0
  131. package/layouts/inner.vue +70 -0
  132. package/nuxt.config.ts +86 -0
  133. package/package.json +38 -0
  134. package/pages/about-us.vue +12 -0
  135. package/pages/address.vue +10 -0
  136. package/pages/cart.vue +10 -0
  137. package/pages/categories.vue +10 -0
  138. package/pages/change-password.vue +10 -0
  139. package/pages/checkout.vue +10 -0
  140. package/pages/contact.vue +11 -0
  141. package/pages/dashboard.vue +10 -0
  142. package/pages/edit-address.vue +10 -0
  143. package/pages/edit-profile.vue +10 -0
  144. package/pages/faq.vue +10 -0
  145. package/pages/forgot-password.vue +10 -0
  146. package/pages/login.vue +10 -0
  147. package/pages/my-account.vue +10 -0
  148. package/pages/order-details.vue +10 -0
  149. package/pages/order-history.vue +10 -0
  150. package/pages/privacy-policy.vue +10 -0
  151. package/pages/product-details.vue +10 -0
  152. package/pages/products.vue +10 -0
  153. package/pages/register.vue +10 -0
  154. package/pages/terms-conditions.vue +10 -0
  155. package/pages/wishlist.vue +10 -0
  156. package/plugins/site-init.client.ts +24 -0
  157. package/plugins/vuetify.ts +18 -0
  158. package/types/index.ts +121 -0
  159. package/utils/image.ts +13 -0
  160. 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>