@crm-market/template-shared 1.0.6 → 1.0.8
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/scss/_variables.scss +3 -3
- package/assets/scss/components/_header.scss +1 -1
- package/assets/scss/style.css +4 -4
- package/components/AboutUs/StaticContent.vue +32 -0
- package/components/Cart/CartEmpty.vue +42 -0
- package/components/Cart/CartItem.vue +145 -0
- package/components/Cart/CartSummary.vue +57 -0
- package/components/Cart/ProductQuantity.vue +23 -13
- package/components/Cart/index.vue +68 -150
- package/components/Categories/index.vue +88 -291
- package/components/Checkout/AddressFields.vue +74 -0
- package/components/Checkout/CheckoutForm.vue +75 -0
- package/components/Checkout/CityAutocomplete.vue +68 -0
- package/components/Checkout/DeliveryMethodCard.vue +66 -0
- package/components/Checkout/DeliveryOptions.vue +148 -0
- package/components/Checkout/OrderSummary.vue +116 -0
- package/components/Checkout/PaymentOptions.vue +76 -0
- package/components/Checkout/PickupPoints.vue +46 -0
- package/components/Checkout/PromoCode.vue +166 -0
- package/components/Checkout/index.vue +235 -173
- package/components/Checkout/types.ts +45 -0
- package/components/Common/PageBanner.vue +22 -20
- package/components/Common/ProductCard.vue +139 -115
- package/components/Common/Services.vue +4 -4
- package/components/Contact/ContactForm.vue +44 -74
- package/components/Contact/ContactInfo.vue +35 -61
- package/components/FAQ/index.vue +6 -119
- package/components/Layout/Copyright.vue +11 -9
- package/components/Layout/Footer.vue +49 -136
- package/components/Layout/HeaderBar.vue +174 -0
- package/components/Layout/HeaderLogo.vue +27 -0
- package/components/Layout/MiddleHeader.vue +1 -228
- package/components/Layout/MiddleHeaderThree.vue +1 -203
- package/components/Layout/MiddleHeaderTwo.vue +1 -239
- package/components/Layout/NavLinks.vue +23 -0
- package/components/Layout/Navbar.vue +38 -151
- package/components/Layout/NavbarStyleFour.vue +1 -332
- package/components/Layout/NavbarStyleThree.vue +1 -188
- package/components/Layout/NavbarStyleTwo.vue +1 -108
- package/components/Layout/Preloader.vue +5 -12
- package/components/Layout/ResponsiveNavbar.vue +32 -418
- package/components/Layout/SitewideBanner.vue +285 -0
- package/components/Layout/TopHeader.vue +17 -83
- package/components/Layout/layoutData.ts +83 -0
- package/components/PrivacyPolicy/index.vue +6 -110
- package/components/ProductDetails/ProductDetailsTab.vue +31 -321
- package/components/ProductDetails/ProductGallery.vue +45 -0
- package/components/ProductDetails/ProductQuantity.vue +8 -14
- package/components/ProductDetails/RecentlyViewed.vue +53 -289
- package/components/ProductDetails/RelatedProducts.vue +64 -0
- package/components/ProductDetails/index.vue +90 -230
- package/components/Products/index.vue +78 -16
- package/components/TermsConditions/index.vue +6 -110
- package/components/Wishlist/index.vue +62 -88
- package/composables/useCart.ts +133 -81
- package/composables/useCategories.ts +67 -31
- package/composables/useCategoryImages.ts +45 -0
- package/composables/useCheckout.ts +302 -97
- package/composables/useContactForm.ts +57 -0
- package/composables/useHomeContent.ts +220 -0
- package/composables/useI18n.ts +12 -24
- package/composables/useJsonLd.ts +162 -0
- package/composables/useProducts.ts +244 -83
- package/composables/useRecent.ts +30 -0
- package/composables/useSanitize.ts +76 -0
- package/composables/useSiteConfig.ts +51 -47
- package/composables/useStaticPages.ts +48 -0
- package/composables/useStoreOrigin.ts +32 -0
- package/composables/useTemplateSections.ts +51 -3
- package/composables/useThemeVars.ts +113 -0
- package/composables/useWishlist.ts +72 -0
- package/layouts/default.vue +80 -19
- package/locales/en.json +255 -86
- package/locales/uk.json +251 -82
- package/modules/template-schema.ts +35 -0
- package/nuxt.config.ts +8 -11
- package/package.json +1 -1
- package/pages/about-us.vue +9 -9
- package/pages/cart.vue +0 -1
- package/pages/categories.vue +0 -1
- package/pages/category/[slug].vue +84 -0
- package/pages/checkout.vue +0 -1
- package/pages/contact.vue +6 -6
- package/pages/delivery.vue +12 -0
- package/pages/faq.vue +6 -6
- package/pages/order-success.vue +113 -0
- package/pages/privacy-policy.vue +6 -6
- package/pages/product/[slug].vue +82 -0
- package/pages/product-details.vue +14 -6
- package/pages/products.vue +12 -1
- package/pages/returns.vue +12 -0
- package/pages/terms-conditions.vue +6 -6
- package/pages/wishlist.vue +2 -2
- package/utils/image.ts +0 -1
- package/utils/seo-urls.ts +70 -0
- package/utils/store-origin.ts +36 -0
- package/components/AboutUs/AboutUsTuan.vue +0 -25
- package/components/AboutUs/Statistics.vue +0 -42
- package/components/AboutUs/SubscribeToTheNewsletter.vue +0 -57
- package/components/AddAddress/index.vue +0 -68
- package/components/BestSellers/Products.vue +0 -1562
- package/components/BestSellers/RecentlyViewed.vue +0 -304
- package/components/ChangePassword/index.vue +0 -69
- package/components/Common/DashboardNavigation.vue +0 -35
- package/components/Dashboard/RecentOrder.vue +0 -173
- package/components/Dashboard/index.vue +0 -77
- package/components/EditAddress/index.vue +0 -117
- package/components/EditProfile/index.vue +0 -95
- package/components/FeaturedProduct/FeaturedProducts.vue +0 -304
- package/components/FeaturedProduct/Products.vue +0 -1562
- package/components/ForgotPassword/index.vue +0 -49
- package/components/Layout/FooterTwo.vue +0 -159
- package/components/Layout/LocationOption.vue +0 -178
- package/components/Layout/RTLSwitchBtn.vue +0 -40
- package/components/Login/index.vue +0 -89
- package/components/MyAccount/index.vue +0 -154
- package/components/NewArrivals/Products.vue +0 -1969
- package/components/NewArrivals/RecentlyViewed.vue +0 -304
- package/components/OrderDetails/index.vue +0 -75
- package/components/OrderHistory/index.vue +0 -195
- package/components/Products/RecentlyViewed.vue +0 -302
- package/components/Register/index.vue +0 -83
- package/components/TrendingProducts/FeaturedProducts.vue +0 -304
- package/components/TrendingProducts/Products.vue +0 -1564
- package/components/Wishlist/ProductQuantity.vue +0 -29
- package/layouts/inner.vue +0 -70
- package/locales/ar.json +0 -234
- package/locales/ru.json +0 -234
- package/pages/address.vue +0 -11
- package/pages/change-password.vue +0 -11
- package/pages/dashboard.vue +0 -11
- package/pages/edit-address.vue +0 -11
- package/pages/edit-profile.vue +0 -11
- package/pages/forgot-password.vue +0 -11
- package/pages/login.vue +0 -11
- package/pages/my-account.vue +0 -11
- package/pages/order-details.vue +0 -11
- package/pages/order-history.vue +0 -11
- package/pages/register.vue +0 -11
package/components/FAQ/index.vue
CHANGED
|
@@ -1,121 +1,8 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
<h2>Frequently Ask & Question</h2>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Питання та відповіді: HTML продавця або дефолтний текст
|
|
3
|
+
const { pageHtml } = useStaticPages();
|
|
4
|
+
</script>
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
<p>
|
|
10
|
-
Selecting the right laptop depends on your specific requirements.
|
|
11
|
-
Consider factors such as intended use (work, gaming, or multimedia),
|
|
12
|
-
processing power, storage capacity, and portability. Our
|
|
13
|
-
knowledgeable staff can guide you based on your preferences and
|
|
14
|
-
budget.
|
|
15
|
-
</p>
|
|
16
|
-
</div>
|
|
17
|
-
<div class="mb-40">
|
|
18
|
-
<h3>What's the difference between LED and OLED TVs?</h3>
|
|
19
|
-
<p>
|
|
20
|
-
LED TVs use a backlight to illuminate pixels, while OLED TVs have
|
|
21
|
-
pixels that emit their own light. OLED TVs generally offer deeper
|
|
22
|
-
blacks and more vibrant colors. The choice depends on your viewing
|
|
23
|
-
preferences and budget. We can help you understand the differences
|
|
24
|
-
and find the perfect TV for your space.
|
|
25
|
-
</p>
|
|
26
|
-
</div>
|
|
27
|
-
<div class="mb-40">
|
|
28
|
-
<h3>How can I protect my devices from malware and viruses?</h3>
|
|
29
|
-
<p>
|
|
30
|
-
Keeping your devices secure is crucial. Install reputable antivirus
|
|
31
|
-
software, keep your operating system and applications up to date,
|
|
32
|
-
and be cautious with email attachments and links. We offer a range
|
|
33
|
-
of security solutions and can provide advice on best practices for
|
|
34
|
-
maintaining a secure digital environment.
|
|
35
|
-
</p>
|
|
36
|
-
</div>
|
|
37
|
-
<div class="mb-40">
|
|
38
|
-
<h3>What's the best way to enhance home audio systems?</h3>
|
|
39
|
-
<p>
|
|
40
|
-
Improving your home audio experience can involve upgrading speakers,
|
|
41
|
-
investing in a soundbar, or setting up a home theater system.
|
|
42
|
-
Consider the size of your room, your preferred audio quality, and
|
|
43
|
-
the type of content you enjoy. Our audio experts can recommend
|
|
44
|
-
solutions tailored to your needs.
|
|
45
|
-
</p>
|
|
46
|
-
</div>
|
|
47
|
-
<div class="mb-40">
|
|
48
|
-
<h3>Do you offer tech support and repair services?</h3>
|
|
49
|
-
<p>
|
|
50
|
-
Yes, we provide tech support and repair services for a variety of
|
|
51
|
-
devices. Whether you need assistance with software issues, hardware
|
|
52
|
-
repairs, or upgrades, our skilled technicians are here to help.
|
|
53
|
-
Contact us for information on our repair services and support
|
|
54
|
-
options.
|
|
55
|
-
</p>
|
|
56
|
-
</div>
|
|
57
|
-
<div class="mb-40">
|
|
58
|
-
<h3>Do you offer online shopping and home delivery services?</h3>
|
|
59
|
-
<p>
|
|
60
|
-
Yes, we provide convenient online shopping options, and our home
|
|
61
|
-
delivery service allows you to order groceries from the comfort of
|
|
62
|
-
your home. Simply browse our online store, place your order, and
|
|
63
|
-
have it delivered to your doorstep at your preferred time.
|
|
64
|
-
</p>
|
|
65
|
-
</div>
|
|
66
|
-
<div class="mb-40">
|
|
67
|
-
<h3>
|
|
68
|
-
What measures do you take to ensure the freshness of your produce?
|
|
69
|
-
</h3>
|
|
70
|
-
<p>
|
|
71
|
-
We prioritize the quality and freshness of our products. Our team
|
|
72
|
-
follows stringent sourcing standards, and our produce is regularly
|
|
73
|
-
restocked to ensure you receive the freshest fruits, vegetables, and
|
|
74
|
-
other perishables.
|
|
75
|
-
</p>
|
|
76
|
-
</div>
|
|
77
|
-
<div class="mb-40">
|
|
78
|
-
<h3>
|
|
79
|
-
Are there options for customers with dietary restrictions or
|
|
80
|
-
preferences?
|
|
81
|
-
</h3>
|
|
82
|
-
<p>
|
|
83
|
-
Absolutely! We strive to cater to various dietary needs. Our store
|
|
84
|
-
offers a diverse range of products, including gluten-free, organic,
|
|
85
|
-
and vegetarian options. Look for special labels or ask our staff for
|
|
86
|
-
assistance in finding products that meet your specific requirements.
|
|
87
|
-
</p>
|
|
88
|
-
</div>
|
|
89
|
-
<div class="mb-40">
|
|
90
|
-
<h3>
|
|
91
|
-
Do you have a loyalty program or discounts for regular customers?
|
|
92
|
-
</h3>
|
|
93
|
-
<p>
|
|
94
|
-
Yes, we value our loyal customers. We offer a loyalty program where
|
|
95
|
-
you can earn points with each purchase, and these points can be
|
|
96
|
-
redeemed for discounts or special offers. Additionally, keep an eye
|
|
97
|
-
out for exclusive promotions and discounts available to our regular
|
|
98
|
-
shoppers.
|
|
99
|
-
</p>
|
|
100
|
-
</div>
|
|
101
|
-
<div class="mb-0">
|
|
102
|
-
<h3>
|
|
103
|
-
How can I provide feedback or suggest products for your store?
|
|
104
|
-
</h3>
|
|
105
|
-
<p>
|
|
106
|
-
We welcome your feedback and suggestions! You can share your
|
|
107
|
-
thoughts with us through our website, customer service, or
|
|
108
|
-
suggestion boxes in the store. Your input helps us improve our
|
|
109
|
-
services and ensures we meet the needs of our valued customers.
|
|
110
|
-
</p>
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
113
|
-
</div>
|
|
114
|
-
</div>
|
|
6
|
+
<template>
|
|
7
|
+
<AboutUsStaticContent :html="pageHtml('faq')" />
|
|
115
8
|
</template>
|
|
116
|
-
|
|
117
|
-
<script>
|
|
118
|
-
export default {
|
|
119
|
-
name: "FAQ",
|
|
120
|
-
};
|
|
121
|
-
</script>
|
|
@@ -1,23 +1,25 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useLayoutData } from './layoutData';
|
|
3
|
+
const { t } = useI18n();
|
|
4
|
+
const { siteName } = useLayoutData();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
1
7
|
<template>
|
|
2
8
|
<div class="copyright-area bg-white">
|
|
3
9
|
<div class="container">
|
|
4
10
|
<v-row class="align-items-center">
|
|
5
11
|
<v-col cols="12" lg="6" md="6">
|
|
6
12
|
<p class="mb-2 mb-md-0 text-center text-md-start">
|
|
7
|
-
©
|
|
8
|
-
<a href="https://envytheme.com/" target="_blank">EnvyTheme</a>
|
|
13
|
+
© {{ new Date().getFullYear() }} {{ siteName }}
|
|
9
14
|
</p>
|
|
10
15
|
</v-col>
|
|
11
16
|
<v-col cols="12" lg="6" md="6">
|
|
12
|
-
<
|
|
13
|
-
<
|
|
14
|
-
|
|
17
|
+
<ul class="list-unstyled d-flex flex-wrap justify-content-center justify-content-md-end gap-3 mb-0">
|
|
18
|
+
<li><NuxtLink to="/privacy-policy">{{ t('layout.footer.privacy') }}</NuxtLink></li>
|
|
19
|
+
<li><NuxtLink to="/terms-conditions">{{ t('layout.footer.terms') }}</NuxtLink></li>
|
|
20
|
+
</ul>
|
|
15
21
|
</v-col>
|
|
16
22
|
</v-row>
|
|
17
23
|
</div>
|
|
18
24
|
</div>
|
|
19
25
|
</template>
|
|
20
|
-
|
|
21
|
-
<script setup lang="ts">
|
|
22
|
-
const { t } = useI18n();
|
|
23
|
-
</script>
|
|
@@ -1,168 +1,81 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useLayoutData } from './layoutData';
|
|
3
|
+
// Футер: дані магазину з конфігу CRM
|
|
4
|
+
const { t } = useI18n();
|
|
5
|
+
const { topLevelCategories } = useCategories();
|
|
6
|
+
const { siteName, siteDescription, phones, email, address, workingHours, socials } = useLayoutData();
|
|
7
|
+
|
|
8
|
+
const footerCategories = computed(() => topLevelCategories.value.slice(0, 6));
|
|
9
|
+
// Колонку «Контакти» показуємо, лише якщо є хоч одне значення
|
|
10
|
+
const hasContacts = computed(() => !!(phones.value.length || email.value || address.value || workingHours.value));
|
|
11
|
+
</script>
|
|
12
|
+
|
|
1
13
|
<template>
|
|
2
14
|
<div class="footer-area pb-60 bg-gray1">
|
|
3
15
|
<div class="container">
|
|
4
16
|
<div class="border-top pt-60 pb-35">
|
|
5
|
-
<v-row class="justify-content-
|
|
6
|
-
<v-col cols="12" lg="
|
|
17
|
+
<v-row class="justify-content-start">
|
|
18
|
+
<v-col cols="12" lg="4" sm="6">
|
|
7
19
|
<div class="single-footer-widget">
|
|
8
|
-
<h3>{{
|
|
9
|
-
<p v-if="
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
<span class="title">{{ t('footer.gotQuestion') }}</span>
|
|
18
|
-
<div class="d-flex align-items-center">
|
|
19
|
-
<i class="flaticon-headphones-1"></i>
|
|
20
|
-
<a :href="`tel:${phone}`">{{ phone }}</a>
|
|
21
|
-
</div>
|
|
22
|
-
</div>
|
|
23
|
-
<div class="call-info" v-if="email">
|
|
24
|
-
<a :href="`mailto:${email}`">{{ email }}</a>
|
|
25
|
-
</div>
|
|
26
|
-
<div class="mt-2" v-if="address">
|
|
27
|
-
<small class="text-muted">{{ address }}</small>
|
|
28
|
-
</div>
|
|
20
|
+
<h3>{{ siteName }}</h3>
|
|
21
|
+
<p v-if="siteDescription">{{ siteDescription }}</p>
|
|
22
|
+
<ul v-if="socials.length" class="ps-0 mb-0 mt-3 list-unstyled d-flex align-items-center social-link">
|
|
23
|
+
<li v-for="s in socials" :key="s.key">
|
|
24
|
+
<a :href="s.url" target="_blank" rel="noopener noreferrer" :aria-label="s.label">
|
|
25
|
+
<i :class="s.icon"></i>
|
|
26
|
+
</a>
|
|
27
|
+
</li>
|
|
28
|
+
</ul>
|
|
29
29
|
</div>
|
|
30
30
|
</v-col>
|
|
31
|
-
<v-col cols="
|
|
31
|
+
<v-col cols="6" :lg="hasContacts ? 3 : 4" sm="6">
|
|
32
32
|
<div class="single-footer-widget">
|
|
33
|
-
<h3>{{ t('footer.
|
|
34
|
-
|
|
33
|
+
<h3>{{ t('layout.footer.categories') }}</h3>
|
|
35
34
|
<ul class="ps-0 mb-0 list-unstyled import-link">
|
|
36
35
|
<li v-for="category in footerCategories" :key="category.id">
|
|
37
|
-
<NuxtLink :to="
|
|
38
|
-
{{ category.name }}
|
|
39
|
-
</NuxtLink>
|
|
36
|
+
<NuxtLink :to="categoryPath(category)">{{ category.name }}</NuxtLink>
|
|
40
37
|
</li>
|
|
41
38
|
<li v-if="footerCategories.length === 0">
|
|
42
|
-
<NuxtLink to="/products">{{ t('
|
|
39
|
+
<NuxtLink to="/products">{{ t('layout.menu.catalog') }}</NuxtLink>
|
|
43
40
|
</li>
|
|
44
41
|
</ul>
|
|
45
42
|
</div>
|
|
46
43
|
</v-col>
|
|
47
|
-
<v-col cols="
|
|
44
|
+
<v-col cols="6" :lg="hasContacts ? 2 : 4" sm="6">
|
|
48
45
|
<div class="single-footer-widget">
|
|
49
|
-
<h3>{{ t('footer.
|
|
50
|
-
|
|
46
|
+
<h3>{{ t('layout.footer.info') }}</h3>
|
|
51
47
|
<ul class="ps-0 mb-0 list-unstyled import-link">
|
|
52
|
-
<li>
|
|
53
|
-
|
|
54
|
-
</li>
|
|
55
|
-
<li>
|
|
56
|
-
|
|
57
|
-
</li>
|
|
58
|
-
<li>
|
|
59
|
-
<NuxtLink to="/privacy-policy">{{ t('common.privacyPolicy') }}</NuxtLink>
|
|
60
|
-
</li>
|
|
61
|
-
<li>
|
|
62
|
-
<NuxtLink to="/faq">{{ t('common.helpCenter') }}</NuxtLink>
|
|
63
|
-
</li>
|
|
64
|
-
<li>
|
|
65
|
-
<NuxtLink to="/terms-conditions">{{ t('common.termsConditions') }}</NuxtLink>
|
|
66
|
-
</li>
|
|
48
|
+
<li><NuxtLink to="/about-us">{{ t('layout.menu.about') }}</NuxtLink></li>
|
|
49
|
+
<li><NuxtLink to="/delivery">{{ t('pages.delivery.title') }}</NuxtLink></li>
|
|
50
|
+
<li><NuxtLink to="/returns">{{ t('pages.returns.title') }}</NuxtLink></li>
|
|
51
|
+
<li><NuxtLink to="/contact">{{ t('layout.menu.contact') }}</NuxtLink></li>
|
|
52
|
+
<li><NuxtLink to="/privacy-policy">{{ t('layout.footer.privacy') }}</NuxtLink></li>
|
|
53
|
+
<li><NuxtLink to="/terms-conditions">{{ t('layout.footer.terms') }}</NuxtLink></li>
|
|
67
54
|
</ul>
|
|
68
55
|
</div>
|
|
69
56
|
</v-col>
|
|
70
|
-
<v-col cols="12" lg="3" sm="6">
|
|
57
|
+
<v-col v-if="hasContacts" cols="12" lg="3" sm="6">
|
|
71
58
|
<div class="single-footer-widget">
|
|
72
|
-
<h3>{{ t('footer.
|
|
73
|
-
|
|
59
|
+
<h3>{{ t('layout.footer.contacts') }}</h3>
|
|
74
60
|
<ul class="ps-0 mb-0 list-unstyled import-link">
|
|
75
|
-
<li>
|
|
76
|
-
|
|
77
|
-
</li>
|
|
78
|
-
<li>
|
|
79
|
-
<NuxtLink to="/contact">{{ t('nav.contact') }}</NuxtLink>
|
|
80
|
-
</li>
|
|
81
|
-
<li>
|
|
82
|
-
<NuxtLink to="/categories">{{ t('nav.categories') }}</NuxtLink>
|
|
83
|
-
</li>
|
|
84
|
-
<li>
|
|
85
|
-
<NuxtLink to="/products">{{ t('nav.allProducts') }}</NuxtLink>
|
|
86
|
-
</li>
|
|
61
|
+
<li v-for="p in phones" :key="p"><a :href="`tel:${p}`">{{ p }}</a></li>
|
|
62
|
+
<li v-if="email"><a :href="`mailto:${email}`">{{ email }}</a></li>
|
|
63
|
+
<li v-if="address">{{ address }}</li>
|
|
64
|
+
<li v-if="workingHours">{{ workingHours }}</li>
|
|
87
65
|
</ul>
|
|
88
66
|
</div>
|
|
89
67
|
</v-col>
|
|
90
68
|
</v-row>
|
|
91
69
|
</div>
|
|
92
|
-
<v-row class="align-items-end justify-content-center">
|
|
93
|
-
<v-col cols="12" lg="3" md="6">
|
|
94
|
-
<div class="single-footer-bottom-item">
|
|
95
|
-
<span class="title">{{ t('footer.subscribe') }}</span>
|
|
96
|
-
<form class="subscribe-form position-relative" @submit.prevent>
|
|
97
|
-
<input
|
|
98
|
-
type="text"
|
|
99
|
-
class="form-control"
|
|
100
|
-
:placeholder="t('footer.enterEmail')"
|
|
101
|
-
/>
|
|
102
|
-
<button
|
|
103
|
-
class="btn btn-warning text-white position-absolute top-50 end-0 translate-middle-y"
|
|
104
|
-
>
|
|
105
|
-
{{ t('footer.submitNow') }}
|
|
106
|
-
</button>
|
|
107
|
-
</form>
|
|
108
|
-
</div>
|
|
109
|
-
</v-col>
|
|
110
|
-
<v-col cols="12" lg="4" md="6">
|
|
111
|
-
<div class="single-footer-bottom-item" v-if="hasSocialMedia">
|
|
112
|
-
<ul
|
|
113
|
-
class="ps-0 mb-0 list-unstyled d-flex align-items-center social-link justify-content-center"
|
|
114
|
-
>
|
|
115
|
-
<li>
|
|
116
|
-
<span class="titles">{{ t('footer.followUs') }}:</span>
|
|
117
|
-
</li>
|
|
118
|
-
<li v-if="socialMedia?.facebook">
|
|
119
|
-
<a :href="socialMedia.facebook" target="_blank">
|
|
120
|
-
<i class="ri-facebook-fill"></i>
|
|
121
|
-
</a>
|
|
122
|
-
</li>
|
|
123
|
-
<li v-if="socialMedia?.twitter">
|
|
124
|
-
<a :href="socialMedia.twitter" target="_blank">
|
|
125
|
-
<i class="ri-twitter-x-line"></i>
|
|
126
|
-
</a>
|
|
127
|
-
</li>
|
|
128
|
-
<li v-if="socialMedia?.instagram">
|
|
129
|
-
<a :href="socialMedia.instagram" target="_blank">
|
|
130
|
-
<i class="ri-instagram-line"></i>
|
|
131
|
-
</a>
|
|
132
|
-
</li>
|
|
133
|
-
</ul>
|
|
134
|
-
</div>
|
|
135
|
-
</v-col>
|
|
136
|
-
<v-col cols="12" lg="5" md="6">
|
|
137
|
-
<div class="single-footer-bottom-item">
|
|
138
|
-
<p class="text-center text-lg-end mb-0 text-muted">
|
|
139
|
-
© {{ new Date().getFullYear() }} {{ storeName }}. {{ t('footer.allRightsReserved') }}.
|
|
140
|
-
</p>
|
|
141
|
-
</div>
|
|
142
|
-
</v-col>
|
|
143
|
-
</v-row>
|
|
144
70
|
</div>
|
|
145
71
|
</div>
|
|
146
72
|
</template>
|
|
147
73
|
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const phone = computed(() => contactInfo.value?.phone);
|
|
157
|
-
const email = computed(() => contactInfo.value?.email);
|
|
158
|
-
const address = computed(() => contactInfo.value?.address);
|
|
159
|
-
const socialMedia = computed(() => contactInfo.value?.socialMedia);
|
|
160
|
-
const hasSocialMedia = computed(() => {
|
|
161
|
-
const sm = socialMedia.value;
|
|
162
|
-
return sm && (sm.facebook || sm.twitter || sm.instagram);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
const footerCategories = computed(() =>
|
|
166
|
-
topLevelCategories.value.slice(0, 6)
|
|
167
|
-
);
|
|
168
|
-
</script>
|
|
74
|
+
<style scoped>
|
|
75
|
+
/* Мобільний: колонки в один стовпчик з охайними відступами */
|
|
76
|
+
@media (max-width: 575.98px) {
|
|
77
|
+
.single-footer-widget { margin-bottom: 8px; }
|
|
78
|
+
.single-footer-widget p { overflow-wrap: anywhere; }
|
|
79
|
+
.single-footer-widget li { overflow-wrap: anywhere; }
|
|
80
|
+
}
|
|
81
|
+
</style>
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import stateStore from '~/utils/store';
|
|
3
|
+
import { useLayoutData } from './layoutData';
|
|
4
|
+
// Універсальна середня частина шапки; три варіанти лише передають props
|
|
5
|
+
const props = withDefaults(
|
|
6
|
+
defineProps<{
|
|
7
|
+
withCategories?: boolean;
|
|
8
|
+
btnClass?: string;
|
|
9
|
+
areaClass?: string;
|
|
10
|
+
logoClass?: string;
|
|
11
|
+
searchClass?: string;
|
|
12
|
+
}>(),
|
|
13
|
+
{ withCategories: true, btnClass: 'btn-warning', areaClass: '', logoClass: '', searchClass: '' },
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const { t } = useI18n();
|
|
17
|
+
const router = useRouter();
|
|
18
|
+
const { topLevelCategories } = useCategories();
|
|
19
|
+
const { cart, itemCount, subtotal, isEmpty, removeFromCart } = useCart();
|
|
20
|
+
const { phone, wishlistCount, resolveMedia } = useLayoutData();
|
|
21
|
+
|
|
22
|
+
const searchQuery = ref('');
|
|
23
|
+
const catOpen = ref(false);
|
|
24
|
+
const cartOpen = ref(false);
|
|
25
|
+
|
|
26
|
+
// Пошук веде на каталог з параметром search
|
|
27
|
+
const handleSearch = () => {
|
|
28
|
+
const q = searchQuery.value.trim();
|
|
29
|
+
if (q) router.push(`/products?search=${encodeURIComponent(q)}`);
|
|
30
|
+
};
|
|
31
|
+
const money = (n: number) => `${n.toFixed(2)} ${t('layout.currency')}`;
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<div :class="['middle-header-area ptb-20', areaClass]">
|
|
36
|
+
<div class="container">
|
|
37
|
+
<!-- Мобільний: ряд 1 — логотип + іконки + бургер, ряд 2 — пошук на всю ширину -->
|
|
38
|
+
<div class="hb-row d-flex flex-wrap align-items-center justify-content-between">
|
|
39
|
+
<div class="hb-left d-lg-flex align-items-center middle-header-left justify-content-lg-end">
|
|
40
|
+
<NuxtLink to="/" :class="['hb-logo middle-logo', logoClass]">
|
|
41
|
+
<LayoutHeaderLogo />
|
|
42
|
+
</NuxtLink>
|
|
43
|
+
<div class="hb-search d-flex">
|
|
44
|
+
<div v-if="props.withCategories" class="dropdown d-none d-lg-block">
|
|
45
|
+
<button
|
|
46
|
+
class="dropdown-toggle border border-2 bg-transparent all-categories"
|
|
47
|
+
:class="{ show: catOpen }"
|
|
48
|
+
@click="catOpen = !catOpen"
|
|
49
|
+
>
|
|
50
|
+
{{ t('layout.header.allCategories') }}
|
|
51
|
+
</button>
|
|
52
|
+
<ul class="dropdown-menu" :class="{ toggler: catOpen }">
|
|
53
|
+
<li v-for="category in topLevelCategories" :key="category.id">
|
|
54
|
+
<NuxtLink class="dropdown-item" :to="categoryPath(category)" @click="catOpen = false">
|
|
55
|
+
{{ category.name }}
|
|
56
|
+
</NuxtLink>
|
|
57
|
+
</li>
|
|
58
|
+
<li v-if="topLevelCategories.length === 0">
|
|
59
|
+
<span class="dropdown-item text-muted">{{ t('layout.header.noCategories') }}</span>
|
|
60
|
+
</li>
|
|
61
|
+
</ul>
|
|
62
|
+
</div>
|
|
63
|
+
<div :class="['position-relative hb-search-box', searchClass]">
|
|
64
|
+
<input
|
|
65
|
+
v-model="searchQuery"
|
|
66
|
+
type="text"
|
|
67
|
+
class="form-control border-2"
|
|
68
|
+
:placeholder="t('layout.header.searchPlaceholder')"
|
|
69
|
+
@keyup.enter="handleSearch"
|
|
70
|
+
/>
|
|
71
|
+
<button
|
|
72
|
+
type="button"
|
|
73
|
+
:class="['position-absolute top-50 end-0 translate-middle-y btn fs-14 fw-medium text-white', btnClass]"
|
|
74
|
+
@click="handleSearch"
|
|
75
|
+
>
|
|
76
|
+
<i class="ri-search-line"></i>
|
|
77
|
+
<span class="d-none d-sm-inline-block">{{ t('layout.header.search') }}</span>
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<ul class="hb-right d-flex align-items-center middle-header-right ps-0 mb-0 list-unstyled justify-content-end">
|
|
84
|
+
<li v-if="phone" class="info d-none d-lg-block">
|
|
85
|
+
<a :href="`tel:${phone}`" class="text-decoration-none">
|
|
86
|
+
<div class="d-flex align-items-center">
|
|
87
|
+
<div class="flex-shrink-0"><i class="ri-phone-line bg-danger-10"></i></div>
|
|
88
|
+
<div class="flex-grow-1 ms-10">
|
|
89
|
+
<span>{{ t('layout.header.hotline') }}</span>
|
|
90
|
+
<h3>{{ phone }}</h3>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</a>
|
|
94
|
+
</li>
|
|
95
|
+
<li class="buy-info tops-4 position-relative">
|
|
96
|
+
<NuxtLink to="/wishlist" class="text-decoration-none success-hover-bg" :aria-label="t('layout.header.wishlist')">
|
|
97
|
+
<div class="position-relative me-9">
|
|
98
|
+
<i class="ri-heart-line"></i>
|
|
99
|
+
<ClientOnly><span class="count bg-success">{{ wishlistCount }}</span><template #fallback><span class="count bg-success">0</span></template></ClientOnly>
|
|
100
|
+
</div>
|
|
101
|
+
</NuxtLink>
|
|
102
|
+
</li>
|
|
103
|
+
<li class="buy-info">
|
|
104
|
+
<div class="dropdown">
|
|
105
|
+
<a
|
|
106
|
+
class="text-decoration-none success-hover-bg"
|
|
107
|
+
href="javascript:void(0);"
|
|
108
|
+
:class="{ active: cartOpen }"
|
|
109
|
+
:aria-label="t('layout.header.cart')"
|
|
110
|
+
@click="cartOpen = !cartOpen"
|
|
111
|
+
>
|
|
112
|
+
<div class="position-relative me-9">
|
|
113
|
+
<i class="ri-shopping-cart-line"></i>
|
|
114
|
+
<ClientOnly><span class="count bg-success">{{ itemCount }}</span><template #fallback><span class="count bg-success">0</span></template></ClientOnly>
|
|
115
|
+
</div>
|
|
116
|
+
</a>
|
|
117
|
+
<ul class="dropdown-menu cart-dropdown dropdown-menu-end" :class="{ toggler: cartOpen }">
|
|
118
|
+
<!-- Вміст кошика з localStorage — лише на клієнті, щоб не було hydration-розбіжностей -->
|
|
119
|
+
<ClientOnly>
|
|
120
|
+
<li v-for="item in cart.items.slice(0, 3)" :key="item.productId" class="d-flex justify-content-between align-items-center cart-item">
|
|
121
|
+
<button class="p-0 border-0 bg-transparent delete-btn" :aria-label="t('layout.header.remove')" @click="removeFromCart(item.productId)">
|
|
122
|
+
<i class="ri-delete-bin-7-line"></i>
|
|
123
|
+
</button>
|
|
124
|
+
<NuxtLink class="dropdown-item d-flex align-items-center" :to="productPath({ id: item.productId, name: item.name })" @click="cartOpen = false">
|
|
125
|
+
<img v-if="item.image" :src="resolveMedia(item.image)" :alt="item.name" />
|
|
126
|
+
<div class="ms-3 d-inline-block text-truncate">
|
|
127
|
+
<span>{{ item.name }}</span>
|
|
128
|
+
<h3>{{ money(item.discountPrice || item.price) }}</h3>
|
|
129
|
+
</div>
|
|
130
|
+
</NuxtLink>
|
|
131
|
+
</li>
|
|
132
|
+
<li v-if="isEmpty" class="cart-item text-center text-muted py-3">{{ t('layout.header.cartEmpty') }}</li>
|
|
133
|
+
<template v-else>
|
|
134
|
+
<li class="d-flex justify-content-between align-items-center cart-item">
|
|
135
|
+
<h3 class="fs-15 mb-0">{{ t('layout.header.total') }}</h3>
|
|
136
|
+
<h3 class="fs-15 mb-0">{{ money(subtotal) }}</h3>
|
|
137
|
+
</li>
|
|
138
|
+
<li class="d-flex justify-content-between align-items-center cart-item">
|
|
139
|
+
<NuxtLink to="/cart" class="btn btn-primary fs-14 text-white" @click="cartOpen = false">{{ t('layout.header.viewCart') }}</NuxtLink>
|
|
140
|
+
<NuxtLink to="/checkout" class="btn btn-warning fs-14 text-white" @click="cartOpen = false">{{ t('layout.header.checkout') }}</NuxtLink>
|
|
141
|
+
</li>
|
|
142
|
+
</template>
|
|
143
|
+
</ClientOnly>
|
|
144
|
+
</ul>
|
|
145
|
+
</div>
|
|
146
|
+
</li>
|
|
147
|
+
<li class="d-lg-none hb-burger">
|
|
148
|
+
<button type="button" class="btn p-0 border-0 bg-transparent" :aria-label="t('layout.menu.toggle')" @click="stateStore.onChangeShow">
|
|
149
|
+
<i class="ri-menu-line"></i>
|
|
150
|
+
</button>
|
|
151
|
+
</li>
|
|
152
|
+
</ul>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
</template>
|
|
157
|
+
|
|
158
|
+
<style scoped>
|
|
159
|
+
/* Мобільна розкладка шапки: від lg — оригінальна тема */
|
|
160
|
+
@media (max-width: 991.98px) {
|
|
161
|
+
.hb-left { display: contents; }
|
|
162
|
+
.hb-logo { order: 1; display: block; max-width: 45%; margin: 0 !important; flex: 0 1 auto; }
|
|
163
|
+
.hb-row { row-gap: 0; }
|
|
164
|
+
.hb-logo :deep(img) { max-height: 40px; width: auto; max-width: 100%; }
|
|
165
|
+
.hb-right { order: 2; gap: 14px; }
|
|
166
|
+
.hb-right :deep(.buy-info) { margin-right: 0 !important; }
|
|
167
|
+
.hb-search { order: 3; width: 100%; max-width: 100%; margin-top: 12px; }
|
|
168
|
+
.hb-search-box { width: 100%; }
|
|
169
|
+
.hb-search-box { flex: 1 1 auto; min-width: 0; }
|
|
170
|
+
.hb-search-box :deep(.form-control) { width: 100% !important; }
|
|
171
|
+
.hb-burger i { font-size: 28px; color: var(--primary-color); line-height: 1; }
|
|
172
|
+
.hb-right :deep(.cart-dropdown) { position: absolute; right: 0; max-width: calc(100vw - 32px); }
|
|
173
|
+
}
|
|
174
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useLayoutData } from './layoutData';
|
|
3
|
+
// Логотип магазину: картинка з конфігу або текст з назвою
|
|
4
|
+
defineProps<{ imgClass?: string }>();
|
|
5
|
+
const { logo, siteName } = useLayoutData();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<img v-if="logo" :src="logo" :alt="siteName" :class="imgClass" style="max-height: 48px; width: auto" />
|
|
10
|
+
<span v-else class="header-logo-text fs-4 fw-bold text-dark" :title="siteName">{{ siteName }}</span>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
<style scoped>
|
|
15
|
+
/* Довга назва магазину не повинна ламати шапку: один рядок з трикрапкою */
|
|
16
|
+
.header-logo-text {
|
|
17
|
+
display: inline-block;
|
|
18
|
+
max-width: 260px;
|
|
19
|
+
white-space: nowrap;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
text-overflow: ellipsis;
|
|
22
|
+
vertical-align: middle;
|
|
23
|
+
}
|
|
24
|
+
@media (max-width: 575.98px) {
|
|
25
|
+
.header-logo-text { max-width: 170px; font-size: 1.1rem !important; }
|
|
26
|
+
}
|
|
27
|
+
</style>
|