@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.
Files changed (139) hide show
  1. package/assets/scss/_variables.scss +3 -3
  2. package/assets/scss/components/_header.scss +1 -1
  3. package/assets/scss/style.css +4 -4
  4. package/components/AboutUs/StaticContent.vue +32 -0
  5. package/components/Cart/CartEmpty.vue +42 -0
  6. package/components/Cart/CartItem.vue +145 -0
  7. package/components/Cart/CartSummary.vue +57 -0
  8. package/components/Cart/ProductQuantity.vue +23 -13
  9. package/components/Cart/index.vue +68 -150
  10. package/components/Categories/index.vue +88 -291
  11. package/components/Checkout/AddressFields.vue +74 -0
  12. package/components/Checkout/CheckoutForm.vue +75 -0
  13. package/components/Checkout/CityAutocomplete.vue +68 -0
  14. package/components/Checkout/DeliveryMethodCard.vue +66 -0
  15. package/components/Checkout/DeliveryOptions.vue +148 -0
  16. package/components/Checkout/OrderSummary.vue +116 -0
  17. package/components/Checkout/PaymentOptions.vue +76 -0
  18. package/components/Checkout/PickupPoints.vue +46 -0
  19. package/components/Checkout/PromoCode.vue +166 -0
  20. package/components/Checkout/index.vue +235 -173
  21. package/components/Checkout/types.ts +45 -0
  22. package/components/Common/PageBanner.vue +22 -20
  23. package/components/Common/ProductCard.vue +139 -115
  24. package/components/Common/Services.vue +4 -4
  25. package/components/Contact/ContactForm.vue +44 -74
  26. package/components/Contact/ContactInfo.vue +35 -61
  27. package/components/FAQ/index.vue +6 -119
  28. package/components/Layout/Copyright.vue +11 -9
  29. package/components/Layout/Footer.vue +49 -136
  30. package/components/Layout/HeaderBar.vue +174 -0
  31. package/components/Layout/HeaderLogo.vue +27 -0
  32. package/components/Layout/MiddleHeader.vue +1 -228
  33. package/components/Layout/MiddleHeaderThree.vue +1 -203
  34. package/components/Layout/MiddleHeaderTwo.vue +1 -239
  35. package/components/Layout/NavLinks.vue +23 -0
  36. package/components/Layout/Navbar.vue +38 -151
  37. package/components/Layout/NavbarStyleFour.vue +1 -332
  38. package/components/Layout/NavbarStyleThree.vue +1 -188
  39. package/components/Layout/NavbarStyleTwo.vue +1 -108
  40. package/components/Layout/Preloader.vue +5 -12
  41. package/components/Layout/ResponsiveNavbar.vue +32 -418
  42. package/components/Layout/SitewideBanner.vue +285 -0
  43. package/components/Layout/TopHeader.vue +17 -83
  44. package/components/Layout/layoutData.ts +83 -0
  45. package/components/PrivacyPolicy/index.vue +6 -110
  46. package/components/ProductDetails/ProductDetailsTab.vue +31 -321
  47. package/components/ProductDetails/ProductGallery.vue +45 -0
  48. package/components/ProductDetails/ProductQuantity.vue +8 -14
  49. package/components/ProductDetails/RecentlyViewed.vue +53 -289
  50. package/components/ProductDetails/RelatedProducts.vue +64 -0
  51. package/components/ProductDetails/index.vue +90 -230
  52. package/components/Products/index.vue +78 -16
  53. package/components/TermsConditions/index.vue +6 -110
  54. package/components/Wishlist/index.vue +62 -88
  55. package/composables/useCart.ts +133 -81
  56. package/composables/useCategories.ts +67 -31
  57. package/composables/useCategoryImages.ts +45 -0
  58. package/composables/useCheckout.ts +302 -97
  59. package/composables/useContactForm.ts +57 -0
  60. package/composables/useHomeContent.ts +220 -0
  61. package/composables/useI18n.ts +12 -24
  62. package/composables/useJsonLd.ts +162 -0
  63. package/composables/useProducts.ts +244 -83
  64. package/composables/useRecent.ts +30 -0
  65. package/composables/useSanitize.ts +76 -0
  66. package/composables/useSiteConfig.ts +51 -47
  67. package/composables/useStaticPages.ts +48 -0
  68. package/composables/useStoreOrigin.ts +32 -0
  69. package/composables/useTemplateSections.ts +51 -3
  70. package/composables/useThemeVars.ts +113 -0
  71. package/composables/useWishlist.ts +72 -0
  72. package/layouts/default.vue +80 -19
  73. package/locales/en.json +255 -86
  74. package/locales/uk.json +251 -82
  75. package/modules/template-schema.ts +35 -0
  76. package/nuxt.config.ts +8 -11
  77. package/package.json +1 -1
  78. package/pages/about-us.vue +9 -9
  79. package/pages/cart.vue +0 -1
  80. package/pages/categories.vue +0 -1
  81. package/pages/category/[slug].vue +84 -0
  82. package/pages/checkout.vue +0 -1
  83. package/pages/contact.vue +6 -6
  84. package/pages/delivery.vue +12 -0
  85. package/pages/faq.vue +6 -6
  86. package/pages/order-success.vue +113 -0
  87. package/pages/privacy-policy.vue +6 -6
  88. package/pages/product/[slug].vue +82 -0
  89. package/pages/product-details.vue +14 -6
  90. package/pages/products.vue +12 -1
  91. package/pages/returns.vue +12 -0
  92. package/pages/terms-conditions.vue +6 -6
  93. package/pages/wishlist.vue +2 -2
  94. package/utils/image.ts +0 -1
  95. package/utils/seo-urls.ts +70 -0
  96. package/utils/store-origin.ts +36 -0
  97. package/components/AboutUs/AboutUsTuan.vue +0 -25
  98. package/components/AboutUs/Statistics.vue +0 -42
  99. package/components/AboutUs/SubscribeToTheNewsletter.vue +0 -57
  100. package/components/AddAddress/index.vue +0 -68
  101. package/components/BestSellers/Products.vue +0 -1562
  102. package/components/BestSellers/RecentlyViewed.vue +0 -304
  103. package/components/ChangePassword/index.vue +0 -69
  104. package/components/Common/DashboardNavigation.vue +0 -35
  105. package/components/Dashboard/RecentOrder.vue +0 -173
  106. package/components/Dashboard/index.vue +0 -77
  107. package/components/EditAddress/index.vue +0 -117
  108. package/components/EditProfile/index.vue +0 -95
  109. package/components/FeaturedProduct/FeaturedProducts.vue +0 -304
  110. package/components/FeaturedProduct/Products.vue +0 -1562
  111. package/components/ForgotPassword/index.vue +0 -49
  112. package/components/Layout/FooterTwo.vue +0 -159
  113. package/components/Layout/LocationOption.vue +0 -178
  114. package/components/Layout/RTLSwitchBtn.vue +0 -40
  115. package/components/Login/index.vue +0 -89
  116. package/components/MyAccount/index.vue +0 -154
  117. package/components/NewArrivals/Products.vue +0 -1969
  118. package/components/NewArrivals/RecentlyViewed.vue +0 -304
  119. package/components/OrderDetails/index.vue +0 -75
  120. package/components/OrderHistory/index.vue +0 -195
  121. package/components/Products/RecentlyViewed.vue +0 -302
  122. package/components/Register/index.vue +0 -83
  123. package/components/TrendingProducts/FeaturedProducts.vue +0 -304
  124. package/components/TrendingProducts/Products.vue +0 -1564
  125. package/components/Wishlist/ProductQuantity.vue +0 -29
  126. package/layouts/inner.vue +0 -70
  127. package/locales/ar.json +0 -234
  128. package/locales/ru.json +0 -234
  129. package/pages/address.vue +0 -11
  130. package/pages/change-password.vue +0 -11
  131. package/pages/dashboard.vue +0 -11
  132. package/pages/edit-address.vue +0 -11
  133. package/pages/edit-profile.vue +0 -11
  134. package/pages/forgot-password.vue +0 -11
  135. package/pages/login.vue +0 -11
  136. package/pages/my-account.vue +0 -11
  137. package/pages/order-details.vue +0 -11
  138. package/pages/order-history.vue +0 -11
  139. package/pages/register.vue +0 -11
@@ -1,138 +1,162 @@
1
- <template>
2
- <div class="product-card">
3
- <div class="product-img position-relative">
4
- <NuxtLink :to="`/product-details?id=${product.id}`">
5
- <img
6
- :src="productImage"
7
- :alt="product.name"
8
- class="w-100 object-fit-cover"
9
- style="height: 200px;"
10
- />
11
- </NuxtLink>
12
- <div class="product-badges" v-if="hasDiscount">
13
- <span class="badge bg-danger">
14
- -{{ discountPercent }}%
15
- </span>
16
- </div>
17
- <div class="product-actions">
18
- <button
19
- class="btn btn-sm"
20
- :class="inCart ? 'btn-success' : 'btn-outline-primary'"
21
- @click="handleAddToCart"
22
- :title="inCart ? t('product.inCart') : t('product.addToCart')"
23
- >
24
- <i :class="inCart ? 'ri-check-line' : 'ri-shopping-cart-line'"></i>
25
- </button>
26
- </div>
27
- </div>
28
- <div class="product-info p-2">
29
- <NuxtLink :to="`/product-details?id=${product.id}`" class="text-decoration-none">
30
- <h6 class="product-name mb-1 text-truncate">{{ product.name }}</h6>
31
- </NuxtLink>
32
- <div class="product-rating mb-1" v-if="product.rating">
33
- <i
34
- v-for="star in 5"
35
- :key="star"
36
- :class="star <= Math.round(product.rating) ? 'ri-star-fill text-warning' : 'ri-star-line text-muted'"
37
- class="fs-12"
38
- ></i>
39
- <span class="text-muted fs-12 ms-1">({{ product.reviews || 0 }})</span>
40
- </div>
41
- <div class="product-price">
42
- <span v-if="hasDiscount" class="text-decoration-line-through text-muted me-1 fs-13">
43
- ${{ product.price.toFixed(2) }}
44
- </span>
45
- <span class="fw-bold" :class="hasDiscount ? 'text-danger' : ''">
46
- ${{ effectivePrice.toFixed(2) }}
47
- </span>
48
- </div>
49
- </div>
50
- </div>
51
- </template>
52
-
53
1
  <script setup lang="ts">
54
- import { computed } from "vue";
55
- import { resolveImageUrl } from "~/utils/image";
56
-
57
- const { t } = useI18n();
2
+ import { computed, onMounted, ref } from 'vue';
3
+ import { useState } from '#imports';
4
+ import {
5
+ getProductImage,
6
+ getProductPrice,
7
+ getProductOldPrice,
8
+ getProductDiscountLabel,
9
+ formatPrice,
10
+ type Product,
11
+ } from '../../composables/useProducts';
58
12
 
59
- const props = defineProps<{
60
- product: Record<string, any>;
61
- }>();
13
+ const props = defineProps<{ product: Product }>();
62
14
 
15
+ const { t } = useI18n();
63
16
  const { addToCart, isInCart } = useCart();
64
17
 
65
- const productImage = computed(() => {
66
- const images = props.product.images;
67
- if (images && images.length > 0) {
68
- return resolveImageUrl(images[0]);
69
- }
70
- return '';
71
- });
18
+ const WISHLIST_KEY = 'wishlist';
72
19
 
73
- const hasDiscount = computed(() =>
74
- props.product.discountPrice && props.product.discountPrice < props.product.price
75
- );
20
+ // Спільний між картками стан вішліста (id товарів), синхронізований з localStorage
21
+ const wishIds = useState<string[]>('wishlist-ids', () => []);
76
22
 
77
- const effectivePrice = computed(() =>
78
- hasDiscount.value ? props.product.discountPrice : props.product.price
79
- );
23
+ const readWishlist = (): any[] => {
24
+ try {
25
+ const arr = JSON.parse(localStorage.getItem(WISHLIST_KEY) || '[]');
26
+ return Array.isArray(arr) ? arr : [];
27
+ } catch {
28
+ return [];
29
+ }
30
+ };
80
31
 
81
- const discountPercent = computed(() => {
82
- if (!hasDiscount.value) return 0;
83
- return Math.round(((props.product.price - props.product.discountPrice) / props.product.price) * 100);
32
+ onMounted(() => {
33
+ wishIds.value = readWishlist().map((i) => (typeof i === 'string' ? i : i?.id));
84
34
  });
85
35
 
86
- const inCart = computed(() => isInCart(props.product.id));
36
+ const title = computed(() => props.product.title || props.product.name || '');
37
+ const image = computed(() => getProductImage(props.product));
38
+ // Фото не завантажилось — показуємо заглушку того ж розміру
39
+ const imageFailed = ref(false);
40
+ const price = computed(() => getProductPrice(props.product));
41
+ const oldPrice = computed(() => getProductOldPrice(props.product));
42
+ // «-20%» або «-50 ₴» (фіксована знижка) — залежно від типу акції
43
+ const discountLabel = computed(() => getProductDiscountLabel(props.product));
44
+ const link = computed(() => productPath(props.product));
45
+
46
+ // Немає в наявності: stock явно 0 або менше
47
+ const outOfStock = computed(() => typeof props.product.stock === 'number' && props.product.stock <= 0);
48
+ // Кошик живе в localStorage: до монтування показуємо «У кошик», щоб SSR і клієнт збігалися (без hydration mismatch)
49
+ const mounted = ref(false);
50
+ onMounted(() => { mounted.value = true; });
51
+ const inCart = computed(() => mounted.value && isInCart(props.product.id));
52
+ const inWishlist = computed(() => wishIds.value.includes(props.product.id));
87
53
 
88
54
  const handleAddToCart = () => {
89
- if (!inCart.value) {
90
- addToCart({
91
- productId: props.product.id,
92
- name: props.product.name,
93
- price: props.product.price,
94
- discountPrice: props.product.discountPrice,
95
- image: props.product.images?.[0] || '',
96
- });
97
- }
55
+ if (outOfStock.value) return;
56
+ // Кошик: price — ціна без знижки, discountPrice — акційна (salePrice)
57
+ addToCart({
58
+ productId: props.product.id,
59
+ name: title.value,
60
+ price: oldPrice.value || price.value,
61
+ discountPrice: oldPrice.value ? price.value : undefined,
62
+ image: image.value,
63
+ });
64
+ };
65
+
66
+ const toggleWishlist = () => {
67
+ const list = readWishlist().filter((i) => (typeof i === 'string' ? i : i?.id));
68
+ const exists = list.some((i) => (typeof i === 'string' ? i : i.id) === props.product.id);
69
+ const next = exists
70
+ ? list.filter((i) => (typeof i === 'string' ? i : i.id) !== props.product.id)
71
+ : [...list, { id: props.product.id, title: title.value, image: image.value, price: price.value }];
72
+ try {
73
+ localStorage.setItem(WISHLIST_KEY, JSON.stringify(next));
74
+ } catch { /* сховище недоступне — лишаємо лише стан у пам'яті */ }
75
+ wishIds.value = next.map((i) => (typeof i === 'string' ? i : i.id));
98
76
  };
99
77
  </script>
100
78
 
79
+ <template>
80
+ <div class="single-product-telemobile-item">
81
+ <div class="product-img position-relative">
82
+ <!-- Фіксоване співвідношення 1:1: картки з фото, без фото і з битим фото однакової висоти -->
83
+ <NuxtLink :to="link" class="d-block pc-media">
84
+ <img v-if="image && !imageFailed" :src="image" :alt="title" loading="lazy" @error="imageFailed = true" />
85
+ <div v-else class="pc-placeholder d-flex align-items-center justify-content-center bg-light">
86
+ <i class="ri-image-line fs-1 text-muted"></i>
87
+ </div>
88
+ </NuxtLink>
89
+ <div class="d-flex justify-content-between align-items-center position-absolute top-0 start-0 end-0">
90
+ <span v-if="discountLabel" class="minus-count">{{ discountLabel }}</span>
91
+ <span v-else></span>
92
+ <button
93
+ type="button"
94
+ class="border-0 wish-btn"
95
+ :aria-label="t('home.favorite')"
96
+ @click="toggleWishlist"
97
+ >
98
+ <i :class="inWishlist ? 'ri-heart-fill text-danger' : 'ri-heart-line'"></i>
99
+ </button>
100
+ </div>
101
+ </div>
102
+ <div class="product-content">
103
+ <NuxtLink :to="link" class="title pc-title">{{ title }}</NuxtLink>
104
+ <div class="d-flex align-items-center justify-content-between">
105
+ <h3>
106
+ {{ formatPrice(price) }}
107
+ <del v-if="oldPrice">{{ formatPrice(oldPrice) }}</del>
108
+ </h3>
109
+ <div v-if="outOfStock" class="d-flex sold-stock align-items-center">
110
+ <span class="stock text-danger">{{ t('home.outOfStock') }}</span>
111
+ </div>
112
+ </div>
113
+ <button
114
+ type="button"
115
+ class="btn btn-success text-white rounded-pill"
116
+ :disabled="outOfStock"
117
+ @click="handleAddToCart"
118
+ >
119
+ <i :class="inCart ? 'ri-check-line' : 'ri-shopping-cart-line'"></i>
120
+ <span>{{ inCart ? t('home.inCart') : t('home.addToCart') }}</span>
121
+ </button>
122
+ </div>
123
+ </div>
124
+ </template>
125
+
101
126
  <style scoped>
102
- .product-card {
103
- border: 1px solid #eee;
104
- border-radius: 8px;
127
+ .pc-media {
128
+ aspect-ratio: 1 / 1;
105
129
  overflow: hidden;
106
- transition: box-shadow 0.3s;
107
- background: #fff;
108
130
  }
109
- .product-card:hover {
110
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
131
+ .pc-media img {
132
+ width: 100%;
133
+ height: 100%;
134
+ object-fit: contain;
111
135
  }
112
- .product-img {
113
- overflow: hidden;
114
- background: #f8f9fa;
136
+ .pc-placeholder {
137
+ width: 100%;
138
+ height: 100%;
115
139
  }
116
- .product-badges {
117
- position: absolute;
118
- top: 8px;
119
- left: 8px;
120
- }
121
- .product-actions {
122
- position: absolute;
123
- top: 8px;
124
- right: 8px;
125
- opacity: 0;
126
- transition: opacity 0.3s;
127
- }
128
- .product-card:hover .product-actions {
129
- opacity: 1;
130
- }
131
- .product-name {
132
- font-size: 14px;
133
- color: #333;
140
+ /* Назва — максимум 2 рядки з обрізанням; висота завжди на 2 рядки, щоб сітка не «рвалась» */
141
+ .pc-title {
142
+ min-height: 2.6em;
143
+ line-height: 1.3;
144
+ display: -webkit-box;
145
+ -webkit-line-clamp: 2;
146
+ line-clamp: 2;
147
+ -webkit-box-orient: vertical;
148
+ overflow: hidden;
149
+ overflow-wrap: anywhere;
134
150
  }
135
- .product-name:hover {
136
- color: var(--primary-color, #0d6efd);
151
+ /* Компактна картка на мобільному */
152
+ @media (max-width: 575.98px) {
153
+ .single-product-telemobile-item { min-width: 0; }
154
+ .single-product-telemobile-item :deep(.product-content) { padding: 10px; }
155
+ .single-product-telemobile-item :deep(.product-content h3) { font-size: 15px; }
156
+ .single-product-telemobile-item :deep(.product-content del) { font-size: 12px; }
157
+ .single-product-telemobile-item :deep(.product-content .btn) {
158
+ width: 100%; padding: 6px 8px; font-size: 13px; white-space: nowrap;
159
+ }
160
+ .single-product-telemobile-item :deep(.product-content .title) { font-size: 14px; line-height: 1.3; }
137
161
  }
138
162
  </style>
@@ -5,7 +5,7 @@
5
5
  <v-col cols="12" lg="3" sm="6">
6
6
  <div class="d-flex align-items-center single-servives-item">
7
7
  <div class="flex-shrink-0">
8
- <img src="~/assets/images/shipped.png" alt="shipped" />
8
+ <img src="~/assets/images/shipped.png" :alt="t('services.fastDelivery')" />
9
9
  </div>
10
10
  <div class="flex-grow-1 ms-3">
11
11
  <h3>{{ t('services.fastDelivery') }}</h3>
@@ -16,7 +16,7 @@
16
16
  <v-col cols="12" lg="3" sm="6">
17
17
  <div class="d-flex align-items-center single-servives-item">
18
18
  <div class="flex-shrink-0">
19
- <img src="~/assets/images/credit.png" alt="shipped" />
19
+ <img src="~/assets/images/credit.png" :alt="t('services.securedPayment')" />
20
20
  </div>
21
21
  <div class="flex-grow-1 ms-3">
22
22
  <h3>{{ t('services.securedPayment') }}</h3>
@@ -27,7 +27,7 @@
27
27
  <v-col cols="12" lg="3" sm="6">
28
28
  <div class="d-flex align-items-center single-servives-item">
29
29
  <div class="flex-shrink-0">
30
- <img src="~/assets/images/wallet.png" alt="shipped" />
30
+ <img src="~/assets/images/wallet.png" :alt="t('services.moneyBack')" />
31
31
  </div>
32
32
  <div class="flex-grow-1 ms-3">
33
33
  <h3>{{ t('services.moneyBack') }}</h3>
@@ -38,7 +38,7 @@
38
38
  <v-col cols="12" lg="3" sm="6">
39
39
  <div class="d-flex align-items-center single-servives-item">
40
40
  <div class="flex-shrink-0">
41
- <img src="~/assets/images/headphones.png" alt="shipped" />
41
+ <img src="~/assets/images/headphones.png" :alt="t('services.support247')" />
42
42
  </div>
43
43
  <div class="flex-grow-1 ms-3">
44
44
  <h3>{{ t('services.support247') }}</h3>
@@ -1,86 +1,56 @@
1
+ <script setup lang="ts">
2
+ // Форма «Напишіть нам»
3
+ const { t } = useI18n();
4
+ const { form, loading, sent, error, submit } = useContactForm();
5
+ </script>
6
+
1
7
  <template>
2
8
  <div class="contact-area pb-60">
3
9
  <div class="container">
4
10
  <div class="authentication-content p-50 mw-100">
5
- <div class="row">
6
- <div class="col-lg-5 mb-4 mb-lg-0">
7
- <div class="login h-100 text-center">
8
- <img
9
- src="~/assets/images/contact-us.png"
10
- class="h-100 object-fit-cover"
11
- alt="contact-us"
12
- />
13
- </div>
14
- </div>
15
- <div class="col-lg-7">
16
- <div class="authentication-form form-wrap">
17
- <h3 class="title mb-2">{{ t('contact.getInTouch') }}</h3>
18
- <p>{{ t('contact.contactDescription') }}</p>
19
-
20
- <form>
21
- <div class="row">
22
- <div class="col-lg-6">
23
- <div class="form-group mb-4">
24
- <label class="label">{{ t('contact.yourName') }} *</label>
25
- <input
26
- type="text"
27
- class="form-control"
28
- :placeholder="t('contact.enterName')"
29
- />
30
- </div>
31
- </div>
32
- <div class="col-lg-6">
33
- <div class="form-group mb-4">
34
- <label class="label">{{ t('contact.yourEmail') }} *</label>
35
- <input
36
- type="email"
37
- class="form-control"
38
- :placeholder="t('contact.enterEmail')"
39
- />
40
- </div>
41
- </div>
42
- <div class="col-lg-12">
43
- <div class="form-group mb-4">
44
- <label class="label">{{ t('contact.yourSubject') }} *</label>
45
- <input
46
- type="text"
47
- class="form-control"
48
- :placeholder="t('contact.yourSubject')"
49
- />
50
- </div>
51
- </div>
52
- <div class="col-lg-12">
53
- <div class="form-group mb-4">
54
- <label class="label">{{ t('contact.yourMessage') }} *</label>
55
- <textarea
56
- class="form-control"
57
- :placeholder="t('contact.writeMessage')"
58
- cols="30"
59
- rows="5"
60
- ></textarea>
61
- </div>
62
- </div>
11
+ <div class="authentication-form form-wrap">
12
+ <h3 class="title mb-2">{{ t('pages.contact.formTitle') }}</h3>
13
+ <p>{{ t('pages.contact.formDesc') }}</p>
63
14
 
64
- <div class="col-lg-12">
65
- <div class="form-group mb-0">
66
- <button
67
- type="submit"
68
- class="btn btn-warning btn-pill text-white"
69
- >
70
- {{ t('footer.submitNow') }}
71
- </button>
72
- </div>
73
- </div>
15
+ <div v-if="sent" class="alert alert-success" role="status">{{ t('pages.contact.sent') }}</div>
16
+ <form v-else novalidate @submit.prevent="submit">
17
+ <div class="row">
18
+ <div class="col-lg-6">
19
+ <div class="form-group mb-4">
20
+ <label class="label" for="cf-name">{{ t('pages.contact.name') }} *</label>
21
+ <input id="cf-name" v-model="form.name" type="text" class="form-control" :placeholder="t('pages.contact.namePh')" />
22
+ </div>
23
+ </div>
24
+ <div class="col-lg-6">
25
+ <div class="form-group mb-4">
26
+ <label class="label" for="cf-email">{{ t('pages.contact.email') }} *</label>
27
+ <input id="cf-email" v-model="form.email" type="email" class="form-control" :placeholder="t('pages.contact.emailPh')" />
74
28
  </div>
75
- </form>
29
+ </div>
30
+ <div class="col-lg-6">
31
+ <div class="form-group mb-4">
32
+ <label class="label" for="cf-phone">{{ t('pages.contact.phone') }}</label>
33
+ <input id="cf-phone" v-model="form.phone" type="tel" class="form-control" :placeholder="t('pages.contact.phonePh')" />
34
+ </div>
35
+ </div>
36
+ <div class="col-lg-12">
37
+ <div class="form-group mb-4">
38
+ <label class="label" for="cf-msg">{{ t('pages.contact.message') }} *</label>
39
+ <textarea id="cf-msg" v-model="form.message" class="form-control" rows="5" :placeholder="t('pages.contact.messagePh')"></textarea>
40
+ </div>
41
+ </div>
42
+ <div v-if="error" class="col-lg-12">
43
+ <div class="alert alert-danger" role="alert">{{ error }}</div>
44
+ </div>
45
+ <div class="col-lg-12">
46
+ <button type="submit" class="btn btn-warning btn-pill text-white" :disabled="loading">
47
+ {{ loading ? t('pages.contact.sending') : t('pages.contact.send') }}
48
+ </button>
49
+ </div>
76
50
  </div>
77
- </div>
51
+ </form>
78
52
  </div>
79
53
  </div>
80
54
  </div>
81
55
  </div>
82
56
  </template>
83
-
84
- <script setup lang="ts">
85
- const { t } = useI18n();
86
- </script>
@@ -1,74 +1,48 @@
1
+ <script setup lang="ts">
2
+ // Контакти магазину з CRM: телефони, email, адреса, графік, соцмережі
3
+ import { useLayoutData } from '~/components/Layout/layoutData';
4
+
5
+ const { phones, email, address, workingHours, socials } = useLayoutData();
6
+ const { t } = useI18n();
7
+ const hasAny = computed(
8
+ () => phones.value.length || email.value || address.value || workingHours.value || socials.value.length,
9
+ );
10
+ </script>
11
+
1
12
  <template>
2
- <div class="contact-info-area pt-60 pb-35">
13
+ <div v-if="hasAny" class="contact-info-area pt-60 pb-35">
3
14
  <div class="container">
4
15
  <div class="row justify-content-center">
5
- <div class="col-lg-4 col-md-6">
6
- <div class="single-contact-info-item">
7
- <div class="d-sm-flex">
8
- <div class="flex-shrink-0">
9
- <i class="flaticon-placeholder"></i>
10
- </div>
11
- <div class="flex-grow-1 ms-sm-3 mt-3 mt-sm-0">
12
- <h3>United Kingdom</h3>
13
- <p>6391 Elgin St. Celina, Delaware 10299</p>
14
- <a href="#" class="tell">(208) 555-0112</a>
15
- <a
16
- href="mailto:support@tuaninfo.com"
17
- class="text-decoration-underline text-primary"
18
- >
19
- support@tuaninfo.com
20
- </a>
21
- </div>
22
- </div>
16
+ <div v-if="phones.length || email" class="col-lg-4 col-md-6 mb-4">
17
+ <div class="single-contact-info-item h-100">
18
+ <h3>{{ t('pages.contact.connect') }}</h3>
19
+ <a v-for="p in phones" :key="p" :href="`tel:${p}`" class="tell d-block">{{ p }}</a>
20
+ <a v-if="email" :href="`mailto:${email}`" class="text-decoration-underline text-primary">{{ email }}</a>
23
21
  </div>
24
22
  </div>
25
- <div class="col-lg-4 col-md-6">
26
- <div class="single-contact-info-item">
27
- <div class="d-sm-flex">
28
- <div class="flex-shrink-0">
29
- <i class="flaticon-placeholder"></i>
30
- </div>
31
- <div class="flex-grow-1 ms-sm-3 mt-3 mt-sm-0">
32
- <h3>Toronto Canada</h3>
33
- <p>1901 Thornridge Cir. Shiloh, Hawaii</p>
34
- <a href="tel:(252)555-0126" class="tell">(252) 555-0126</a>
35
- <a
36
- href="mailto:support@tuaninfo.com"
37
- class="text-decoration-underline text-primary"
38
- >
39
- support@tuaninfo.com
40
- </a>
41
- </div>
42
- </div>
23
+ <div v-if="address || workingHours" class="col-lg-4 col-md-6 mb-4">
24
+ <div class="single-contact-info-item h-100">
25
+ <h3>{{ t('pages.contact.addressHours') }}</h3>
26
+ <p v-if="address">{{ address }}</p>
27
+ <p v-if="workingHours" class="mb-0">{{ workingHours }}</p>
43
28
  </div>
44
29
  </div>
45
- <div class="col-lg-4 col-md-6">
46
- <div class="single-contact-info-item">
47
- <div class="d-sm-flex">
48
- <div class="flex-shrink-0">
49
- <i class="flaticon-placeholder"></i>
50
- </div>
51
- <div class="flex-grow-1 ms-sm-3 mt-3 mt-sm-0">
52
- <h3>Saint Barthélemy</h3>
53
- <p>4517 Washington Ave. Manchester</p>
54
- <a href="tel:(505)555-0125" class="tell">(505)555-0125</a>
55
- <a
56
- href="mailto:support@tuaninfo.com"
57
- class="text-decoration-underline text-primary"
58
- >
59
- support@tuaninfo.com
60
- </a>
61
- </div>
62
- </div>
30
+ <div v-if="socials.length" class="col-lg-4 col-md-6 mb-4">
31
+ <div class="single-contact-info-item h-100">
32
+ <h3>{{ t('pages.contact.socials') }}</h3>
33
+ <a
34
+ v-for="s in socials"
35
+ :key="s.key"
36
+ :href="s.url"
37
+ target="_blank"
38
+ rel="noopener noreferrer"
39
+ class="d-block text-primary"
40
+ >
41
+ <i :class="s.icon" class="me-1"></i>{{ s.label }}
42
+ </a>
63
43
  </div>
64
44
  </div>
65
45
  </div>
66
46
  </div>
67
47
  </div>
68
48
  </template>
69
-
70
- <script>
71
- export default {
72
- name: "ContactInfo",
73
- };
74
- </script>