@meeovi/layer-commerce 1.0.10 → 1.0.13
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/app/components/catalog/product/ProductAccordion/ProductAccordion.vue +39 -0
- package/app/components/catalog/product/ProductAccordion/__tests__/ProductAccordion.spec.ts +15 -0
- package/app/components/catalog/product/ProductAccordion/types.ts +5 -0
- package/app/components/catalog/product/ProductProperties/ProductProperties.vue +50 -0
- package/app/components/catalog/product/ProductProperties/__tests__/ProductProperties.spec.ts +15 -0
- package/app/components/catalog/product/ProductProperties/types.ts +5 -0
- package/app/components/catalog/product/ProductSlider/ProductSlider.vue +28 -0
- package/app/components/catalog/product/ProductSlider/__tests__/ProductSlider.spec.ts +14 -0
- package/app/components/catalog/product/ProductSlider/types.ts +7 -0
- package/app/components/catalog/product/RecommendedProducts/RecommendedProducts.vue +12 -0
- package/app/components/catalog/product/RecommendedProducts/types.ts +5 -0
- package/app/components/catalog/product/RenderContentProductSlider/RenderContentProductSlider.vue +11 -0
- package/app/components/catalog/product/add-attribute.vue +54 -0
- package/app/components/catalog/product/add-product-type.vue +54 -0
- package/app/components/catalog/product/add-product.vue +53 -0
- package/app/components/catalog/product/add-showcase.vue +52 -0
- package/app/components/catalog/product/add-station.vue +54 -0
- package/app/components/catalog/product/bestsellers.vue +57 -0
- package/app/components/catalog/product/bidding.vue +93 -0
- package/app/components/catalog/product/colorOptions.vue +58 -0
- package/app/components/catalog/product/deals.vue +46 -0
- package/app/components/catalog/product/exclusives.vue +56 -0
- package/app/components/catalog/product/featuredproducts.vue +57 -0
- package/app/components/catalog/product/giftCard.vue +63 -0
- package/app/components/catalog/product/latestproducts.vue +46 -0
- package/app/components/catalog/product/productCard.vue +105 -0
- package/app/components/catalog/product/productCompare.vue +60 -0
- package/app/components/catalog/product/productCompareTable.vue +441 -0
- package/app/components/catalog/product/productDetails.vue +126 -0
- package/app/components/catalog/product/productFaqs.vue +17 -0
- package/app/components/catalog/product/productGallery.vue +16 -0
- package/app/components/catalog/product/productQty.vue +54 -0
- package/app/components/catalog/product/productReviews.vue +56 -0
- package/app/components/catalog/product/productSpecs.vue +116 -0
- package/app/components/catalog/product/radiostation.vue +36 -0
- package/app/components/catalog/product/recentlyviewed.vue +43 -0
- package/app/components/catalog/product/relatedbrands.vue +54 -0
- package/app/components/catalog/product/relatedproducts.vue +43 -0
- package/app/components/catalog/product/relatedstations.vue +40 -0
- package/app/components/catalog/product/shippingOptions.vue +41 -0
- package/app/components/catalog/product/sizeOptions.vue +42 -0
- package/app/components/catalog/product/update-attribute-set.vue +209 -0
- package/app/components/catalog/product/update-attribute.vue +118 -0
- package/app/components/catalog/product/update-product.vue +372 -0
- package/app/components/catalog/product/update-showcase.vue +153 -0
- package/app/components/catalog/shops/relatedstores.vue +37 -0
- package/app/components/catalog/shops/restaurant.vue +66 -0
- package/app/components/catalog/shops/stores.vue +44 -0
- package/app/components/catalog/vendor/README.md +3 -0
- package/app/components/catalog/vendor/blocks/biggestcustomers.vue +33 -0
- package/app/components/catalog/vendor/blocks/lowestselling.vue +33 -0
- package/app/components/catalog/vendor/blocks/topcategories.vue +33 -0
- package/app/components/catalog/vendor/blocks/topproducts.vue +27 -0
- package/app/components/catalog/vendor/pages/attributes.vue +43 -0
- package/app/components/catalog/vendor/pages/commissions.vue +43 -0
- package/app/components/catalog/vendor/pages/crm.vue +67 -0
- package/app/components/catalog/vendor/pages/dashboard.vue +46 -0
- package/app/components/catalog/vendor/pages/emails.vue +43 -0
- package/app/components/catalog/vendor/pages/enquiries.vue +43 -0
- package/app/components/catalog/vendor/pages/invoices.vue +43 -0
- package/app/components/catalog/vendor/pages/orders.vue +68 -0
- package/app/components/catalog/vendor/pages/products.vue +55 -0
- package/app/components/catalog/vendor/pages/reviews.vue +48 -0
- package/app/components/catalog/vendor/pages/shipments.vue +43 -0
- package/app/components/catalog/vendor/pages/stores.vue +43 -0
- package/app/components/categories/chart/[id].vue +200 -0
- package/app/components/categories/chart/add-chart.vue +142 -0
- package/app/components/categories/chart/chart.vue +82 -0
- package/app/components/categories/chart/monthlyChart.vue +46 -0
- package/app/components/categories/chart/weeklyChart.vue +46 -0
- package/app/components/categories/chart/yearlyChart.vue +46 -0
- package/app/components/categories/charts.vue +118 -0
- package/app/components/categories/deals.vue +101 -0
- package/app/components/categories/eats.vue +49 -0
- package/app/components/categories/restaurants.vue +26 -0
- package/app/components/categories/station/[id].vue +72 -0
- package/app/components/categories/stations.vue +124 -0
- package/app/components/categories/time/time.vue +63 -0
- package/app/components/categories/travel.vue +75 -0
- package/app/components/categories/weather/weather.vue +44 -0
- package/app/components/content/blocks/breadcrumbs.vue +0 -0
- package/app/components/content/blocks/currencySwitcher.vue +0 -0
- package/app/components/content/blocks/languageSwitcher.vue +0 -0
- package/app/components/content/blocks/videoproduct.vue +9 -0
- package/app/components/content/pages/checkout.vue +118 -0
- package/app/components/content/pages/meeoviGlobal.vue +68 -0
- package/app/components/content/pages/pickup-locations.vue +238 -0
- package/app/components/content/pages/showcases.vue +90 -0
- package/app/components/content/pages/success.vue +60 -0
- package/app/components/marketing/add-brand.vue +54 -0
- package/app/components/marketing/add-incentive.vue +54 -0
- package/app/components/marketing/promotions/giftcards.vue +102 -0
- package/app/components/marketing/promotions/subscriptions.vue +121 -0
- package/app/components/marketing/update-incentive.vue +326 -0
- package/app/components/menus/lowernav.vue +78 -0
- package/app/components/partials/LocaleSelector.vue +24 -0
- package/app/components/partials/ShoppingCart.vue +128 -0
- package/app/components/partials/StripePayment.vue +149 -0
- package/app/components/partials/addToCartBtn.vue +40 -0
- package/app/components/partials/cartItem.vue +124 -0
- package/app/components/partials/checkoutButton.vue +44 -0
- package/app/components/partials/compareBtn.vue +68 -0
- package/app/components/partials/ratings.vue +13 -0
- package/app/components/partials/store/CurrencySelector.vue +133 -0
- package/app/components/partials/store/StoreSwitcher.vue +13 -0
- package/app/components/placeholders/Comments.vue +15 -0
- package/app/components/placeholders/CreateListBtn.vue +7 -0
- package/app/components/placeholders/Event.vue +9 -0
- package/app/components/placeholders/ListShowcases.vue +9 -0
- package/app/components/placeholders/Short.vue +9 -0
- package/app/components/placeholders/Space.vue +9 -0
- package/app/components/placeholders/Tag.vue +7 -0
- package/app/components/related/brandCard.vue +41 -0
- package/app/components/related/incentiveCard.vue +44 -0
- package/app/components/related/invoiceCard.vue +43 -0
- package/app/components/related/orderCard.vue +43 -0
- package/app/components/related/relatedproducts.vue +17 -0
- package/app/components/sales/CartPageContent/CartPageContent.vue +37 -0
- package/app/components/sales/CheckoutAddress/CheckoutAddress.vue +50 -0
- package/app/components/sales/CheckoutAddress/__tests__/CheckoutAddress.spec.ts +16 -0
- package/app/components/sales/CheckoutAddress/types.ts +16 -0
- package/app/components/sales/CheckoutPayment/CheckoutPayment.vue +68 -0
- package/app/components/sales/CheckoutPayment/__tests__/CheckoutPayment.spec.ts +14 -0
- package/app/components/sales/CheckoutPayment/types.ts +12 -0
- package/app/components/sales/OrderSummary/OrderSummary.vue +57 -0
- package/app/components/sales/OrderSummary/__tests__/ContactInformation.spec.ts +52 -0
- package/app/components/sales/OrderSummary/types.ts +5 -0
- package/app/components/sales/incentives.vue +223 -0
- package/app/components/sales/invoices.vue +107 -0
- package/app/components/sales/orders.vue +378 -0
- package/app/components/sales/shipments.vue +65 -0
- package/app/components/sales/transactions.vue +109 -0
- package/app/components/shop/add-shop.vue +54 -0
- package/app/components/shop/cart/cartItem.vue +182 -0
- package/app/components/shop/cart/checkout.vue +415 -0
- package/app/components/shop/checkout/StripeCardElement.vue +206 -0
- package/app/components/shop/checkout/StripeCheckout.vue +49 -0
- package/app/components/shop/checkout/addressBilling.vue +263 -0
- package/app/components/shop/checkout/addressShipping.vue +175 -0
- package/app/components/shop/checkout/cart/ProductItem.vue +56 -0
- package/app/components/shop/checkout/cart/PromotionItem.vue +53 -0
- package/app/composables/_types.ts +18 -0
- package/app/composables/adapters/abstract/cartAdapter.ts +0 -0
- package/app/composables/adapters/abstract/categoryAdapter.ts +0 -0
- package/app/composables/adapters/abstract/customerAdapter.ts +0 -0
- package/app/composables/adapters/abstract/inventoryAdapter.ts +0 -0
- package/app/composables/adapters/abstract/orderAdapter.ts +0 -0
- package/app/composables/adapters/abstract/productAdapter.ts +7 -0
- package/app/composables/cart/registry.ts +20 -0
- package/app/composables/cart/types.ts +18 -0
- package/app/composables/cart/useCart.ts +15 -0
- package/app/composables/config.ts +19 -0
- package/app/composables/defs/apiDefinitions.ts +55 -0
- package/app/composables/defs/extension.feature +40 -0
- package/app/composables/defs/extension.mocks.ts +39 -0
- package/app/composables/defs/extension.test.ts +280 -0
- package/app/composables/defs/extension.ts +236 -0
- package/app/composables/defs/index.ts +3 -0
- package/app/composables/defs/types.ts +136 -0
- package/app/composables/domain/cart.ts +0 -0
- package/app/composables/domain/category.ts +0 -0
- package/app/composables/domain/order.ts +0 -0
- package/app/composables/domain/price.ts +0 -0
- package/app/composables/domain/product.ts +8 -0
- package/app/composables/domain/reward.ts +0 -0
- package/app/composables/domain/transactions.ts +0 -0
- package/app/composables/helpers/contextualizedNormalizers.feature +14 -0
- package/app/composables/helpers/contextualizedNormalizers.test.ts +85 -0
- package/app/composables/helpers/contextualizedNormalizers.ts +20 -0
- package/app/composables/helpers/index.ts +1 -0
- package/app/composables/index.ts +6 -0
- package/app/composables/methods/auth.ts +83 -0
- package/app/composables/methods/cart.ts +119 -0
- package/app/composables/methods/category.ts +27 -0
- package/app/composables/methods/checkout.ts +54 -0
- package/app/composables/methods/customer.ts +52 -0
- package/app/composables/methods/helpers.ts +5 -0
- package/app/composables/methods/index.ts +75 -0
- package/app/composables/methods/order.ts +39 -0
- package/app/composables/methods/product.ts +95 -0
- package/app/composables/methods/settings.ts +16 -0
- package/app/composables/models/cart.ts +95 -0
- package/app/composables/models/category.ts +13 -0
- package/app/composables/models/checkout.ts +17 -0
- package/app/composables/models/customer.ts +16 -0
- package/app/composables/models/facets.ts +25 -0
- package/app/composables/models/index.ts +94 -0
- package/app/composables/models/order.ts +43 -0
- package/app/composables/models/product.ts +73 -0
- package/app/composables/models/shared.ts +75 -0
- package/app/composables/products/registry.ts +20 -0
- package/app/composables/products/types.ts +13 -0
- package/app/composables/products/useEvents.ts +0 -0
- package/app/composables/products/useGiftCards.ts +0 -0
- package/app/composables/products/useProducts.ts +12 -0
- package/app/composables/products/useSubscriptions.ts +0 -0
- package/app/composables/registry.ts +21 -0
- package/app/composables/stores/cart.ts +218 -0
- package/app/composables/stores/cartStore.ts +300 -0
- package/app/composables/stores/checkout.ts +19 -0
- package/app/composables/stores/compare.ts +65 -0
- package/app/composables/stores/currency.js +29 -0
- package/app/composables/stores/digital-products.js +11 -0
- package/app/composables/stores/index.js +0 -0
- package/app/composables/stores/orders.ts +161 -0
- package/app/composables/stores/product.ts +26 -0
- package/app/composables/stores/productList.ts +0 -0
- package/app/composables/stores/productListInfo.ts +0 -0
- package/app/composables/stores/products.ts +112 -0
- package/app/composables/stores/recentlyViewedProducts.ts +0 -0
- package/app/composables/stores/review.ts +25 -0
- package/app/composables/stores/storeInPickUp.ts +22 -0
- package/app/composables/stores/user.ts +20 -0
- package/app/composables/stores/wishlist.ts +19 -0
- package/app/composables/types/Order.type.ts +181 -0
- package/app/composables/types/index.ts +285 -0
- package/app/composables/types/product.ts +14 -0
- package/app/composables/useBreakpoints/index.ts +1 -0
- package/app/composables/useBreakpoints/useBreakpoints.ts +28 -0
- package/app/composables/useCart/__tests__/useCart.spec.ts +11 -0
- package/app/composables/useCart/index.ts +1 -0
- package/app/composables/useCart/types.ts +17 -0
- package/app/composables/useCart/useCart.ts +46 -0
- package/app/composables/useCartShippingMethods/__tests__/useCartShippingMethods.spec.ts +11 -0
- package/app/composables/useCartShippingMethods/index.ts +1 -0
- package/app/composables/useCartShippingMethods/types.ts +17 -0
- package/app/composables/useCartShippingMethods/useCartShippingMethods.ts +47 -0
- package/app/composables/useCatalog.ts +64 -0
- package/app/composables/useContent/index.ts +1 -0
- package/app/composables/useContent/types.ts +44 -0
- package/app/composables/useContent/useContent.ts +45 -0
- package/app/composables/useContent.ts +57 -0
- package/app/composables/useCustomer/__tests__/useCustomer.spec.ts +25 -0
- package/app/composables/useCustomer/index.ts +2 -0
- package/app/composables/useCustomer/types.ts +17 -0
- package/app/composables/useCustomer/useCustomer.ts +40 -0
- package/app/composables/useCustomerAddress/__tests__/useCustomerAddress.spec.ts +11 -0
- package/app/composables/useCustomerAddress/index.ts +2 -0
- package/app/composables/useCustomerAddress/types.ts +17 -0
- package/app/composables/useCustomerAddress/useCustomerAddress.ts +55 -0
- package/app/composables/useCustomerOrder/__tests__/useCustomerOrder.spec.ts +11 -0
- package/app/composables/useCustomerOrder/adress.ts +10 -0
- package/app/composables/useCustomerOrder/index.ts +2 -0
- package/app/composables/useCustomerOrder/product.ts +37 -0
- package/app/composables/useCustomerOrder/types.ts +40 -0
- package/app/composables/useCustomerOrder/useCustomerOrder.ts +63 -0
- package/app/composables/useCustomerOrders/__tests__/useCustomerOrders.spec.ts +11 -0
- package/app/composables/useCustomerOrders/index.ts +2 -0
- package/app/composables/useCustomerOrders/types.ts +20 -0
- package/app/composables/useCustomerOrders/useCustomerOrders.ts +56 -0
- package/app/composables/useCustomerReturns/__tests__/useCustomerReturns.spec.ts +11 -0
- package/app/composables/useCustomerReturns/index.ts +2 -0
- package/app/composables/useCustomerReturns/types.ts +17 -0
- package/app/composables/useCustomerReturns/useCustomerReturns.ts +41 -0
- package/app/composables/useHandleError/index.ts +1 -0
- package/app/composables/useHandleError/types.ts +11 -0
- package/app/composables/useHandleError/useHandleError.ts +27 -0
- package/app/composables/usePageTitle.ts +16 -0
- package/app/composables/useProduct/index.ts +2 -0
- package/app/composables/useProduct/types.ts +17 -0
- package/app/composables/useProduct/useProduct.ts +42 -0
- package/app/composables/useProductAttribute/__tests__/useProduct.mock.ts +31 -0
- package/app/composables/useProductAttribute/__tests__/useProductAttribute.spec.ts +14 -0
- package/app/composables/useProductAttribute/index.ts +1 -0
- package/app/composables/useProductAttribute/useProductAttribute.ts +37 -0
- package/app/composables/useProductRecommended/__tests__/useProductRecommended.spec.ts +12 -0
- package/app/composables/useProductRecommended/index.ts +1 -0
- package/app/composables/useProductRecommended/types.ts +17 -0
- package/app/composables/useProductRecommended/useProductRecommended.ts +43 -0
- package/app/composables/useProductReviews/__tests__/productReviews.mock.ts +20 -0
- package/app/composables/useProductReviews/__tests__/useProductReviews.spec.ts +22 -0
- package/app/composables/useProductReviews/index.ts +2 -0
- package/app/composables/useProductReviews/types.ts +17 -0
- package/app/composables/useProductReviews/useProductReviews.ts +46 -0
- package/app/composables/useProducts/__tests__/useProducts.spec.ts +11 -0
- package/app/composables/useProducts/types.ts +22 -0
- package/app/composables/useProducts/useProducts.ts +41 -0
- package/app/composables/utils/countryList.ts +14 -0
- package/app/composables/utils/currency.js +56 -0
- package/app/composables/utils/glossary.ts +0 -0
- package/app/composables/utils/importExport.ts +0 -0
- package/app/composables/utils/index.js +0 -0
- package/app/composables/utils/print.ts +0 -0
- package/app/composables/utils/shopThemes.ts +0 -0
- package/app/composables/utils/statistics.ts +0 -0
- package/app/composables/utils/stock.ts +0 -0
- package/app/composables/utils/stripe.ts +16 -0
- package/app/composables/utils/taxation.ts +0 -0
- package/app/composables/utils/tellFriends.ts +0 -0
- package/app/composables/validationRules/index.ts +1 -0
- package/app/composables/validationRules/password.feature +67 -0
- package/app/composables/validationRules/password.test.ts +89 -0
- package/app/composables/validationRules/password.ts +25 -0
- package/app/composables/vendors/index.ts +0 -0
- package/app/composables/vendors/registry.ts +0 -0
- package/app/composables/vendors/useAffiliates.ts +0 -0
- package/app/composables/vendors/useCommission.ts +0 -0
- package/app/modules/vue-head/composables/useHead.ts +3 -0
- package/app/pages/brand/[...slug].vue +92 -0
- package/app/pages/brands.vue +90 -0
- package/app/pages/cart.vue +142 -0
- package/app/pages/compare.vue +166 -0
- package/app/pages/departments/[...slug].vue +385 -0
- package/app/pages/departments/category/[...slug].vue +135 -0
- package/app/pages/incentive/[...id].vue +66 -0
- package/app/pages/invoice/[id].vue +309 -0
- package/app/pages/order/[id].vue +327 -0
- package/app/pages/product/[...id].vue +309 -0
- package/app/pages/product/showcases/index.vue +86 -0
- package/app/pages/shipment/[...id].vue +176 -0
- package/app/pages/shop/[...slug].vue +158 -0
- package/app/pages/shops.vue +76 -0
- package/app/pages/subscription/[...id].vue +147 -0
- package/app/pages/transaction/[...id].vue +74 -0
- package/app/types/shims-imports.d.ts +13 -0
- package/app/utils/client.ts +26 -0
- package/app/utils/index.ts +53 -0
- package/app/utils/normalizer.ts +23 -0
- package/app/utils/normalizers/magento.ts +29 -0
- package/app/utils/normalizers/shopify.ts +29 -0
- package/global.d.ts +149 -0
- package/index.js +3 -0
- package/package.json +3 -9
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-sheet class="pa-4" elevation="2">
|
|
3
|
+
<div class="flex items-center justify-between mb-4">
|
|
4
|
+
<h3 class="text-lg font-semibold">Your Cart ({{ cartStore.itemCount }})</h3>
|
|
5
|
+
<v-btn color="primary" variant="tonal" @click="proceedToCheckout" :disabled="cartStore.isEmpty || cartStore.loading">
|
|
6
|
+
Proceed to Checkout
|
|
7
|
+
</v-btn>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<v-divider />
|
|
11
|
+
|
|
12
|
+
<div v-if="cartStore.loading" class="py-6 text-center">Loading cart…</div>
|
|
13
|
+
|
|
14
|
+
<div v-else>
|
|
15
|
+
<v-list two-line>
|
|
16
|
+
<v-list-item v-for="item in cartItems" :key="item.id" class="py-4">
|
|
17
|
+
<PartialsCartItem :item="item" @cart-changed="initialize" />
|
|
18
|
+
</v-list-item>
|
|
19
|
+
</v-list>
|
|
20
|
+
|
|
21
|
+
<v-divider class="my-4" />
|
|
22
|
+
|
|
23
|
+
<div class="flex flex-col gap-2">
|
|
24
|
+
<div class="flex justify-between">
|
|
25
|
+
<span>Subtotal</span>
|
|
26
|
+
<span>{{ formatPrice(cartStore.subtotal) }}</span>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="flex justify-between">
|
|
29
|
+
<span>Tax</span>
|
|
30
|
+
<span>{{ formatPrice(cartStore.taxAmount) }}</span>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="flex justify-between font-semibold text-lg">
|
|
33
|
+
<span>Total</span>
|
|
34
|
+
<span>{{ formatPrice(cartStore.total) }}</span>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div v-if="cartStore.error" class="mt-4 text-sm text-error">{{ cartStore.error }}</div>
|
|
40
|
+
</v-sheet>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<script setup>
|
|
44
|
+
import { ref, watch, computed, onMounted } from 'vue'
|
|
45
|
+
import { useCartStore } from '~/stores/cart'
|
|
46
|
+
import { useRouter } from 'vue-router'
|
|
47
|
+
|
|
48
|
+
const cartStore = useCartStore()
|
|
49
|
+
const router = useRouter()
|
|
50
|
+
|
|
51
|
+
const placeholder = '/_assets/placeholder.png'
|
|
52
|
+
|
|
53
|
+
const quantities = ref({})
|
|
54
|
+
|
|
55
|
+
const cartItems = computed(() => (cartStore.cart?.items || []))
|
|
56
|
+
|
|
57
|
+
const initialize = async () => {
|
|
58
|
+
await cartStore.initializeCart()
|
|
59
|
+
// initialize local quantity map
|
|
60
|
+
cartItems.value.forEach((it) => {
|
|
61
|
+
quantities.value[it.id] = it.quantity
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
onMounted(() => {
|
|
66
|
+
initialize()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
watch(cartItems, (newItems) => {
|
|
70
|
+
// keep local quantities in sync when items change
|
|
71
|
+
newItems.forEach((it) => {
|
|
72
|
+
if (!quantities.value[it.id]) quantities.value[it.id] = it.quantity
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const getImageUrl = (item) => {
|
|
77
|
+
const file = item.product?.images?.[0]
|
|
78
|
+
const config = useRuntimeConfig()
|
|
79
|
+
if (file && file.id) return `${config.public.directus.url}/assets/${file.id}`
|
|
80
|
+
return placeholder
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const formatPrice = (value) => {
|
|
84
|
+
const amount = typeof value === 'number' ? value : Number(value || 0)
|
|
85
|
+
return new Intl.NumberFormat(undefined, { style: 'currency', currency: cartStore.cart?.currency || 'USD' }).format(amount)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const removeItem = async (item) => {
|
|
89
|
+
await cartStore.removeProductFromCart(item.id)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const updateQty = async (itemId, qty) => {
|
|
93
|
+
qty = Number(qty)
|
|
94
|
+
if (isNaN(qty) || qty < 1) return
|
|
95
|
+
await cartStore.updateProductQuantity(itemId, qty)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const onQtyChange = async (item) => {
|
|
99
|
+
const newQty = quantities.value[item.id]
|
|
100
|
+
await updateQty(item.id, newQty)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const increaseQty = async (item) => {
|
|
104
|
+
quantities.value[item.id] = (quantities.value[item.id] || item.quantity) + 1
|
|
105
|
+
await updateQty(item.id, quantities.value[item.id])
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const decreaseQty = async (item) => {
|
|
109
|
+
const newVal = (quantities.value[item.id] || item.quantity) - 1
|
|
110
|
+
if (newVal <= 0) {
|
|
111
|
+
// remove item
|
|
112
|
+
await removeItem(item)
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
quantities.value[item.id] = newVal
|
|
116
|
+
await updateQty(item.id, newVal)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const proceedToCheckout = () => {
|
|
120
|
+
// navigate to a checkout route; adjust if project uses a different path
|
|
121
|
+
router.push('/checkout')
|
|
122
|
+
}
|
|
123
|
+
</script>
|
|
124
|
+
|
|
125
|
+
<style scoped>
|
|
126
|
+
.v-list-item { align-items: flex-start; }
|
|
127
|
+
.w-24 { width: 6rem; }
|
|
128
|
+
</style>
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<!-- components/StripePayment.vue -->
|
|
2
|
+
<template>
|
|
3
|
+
<div class="stripe-payment-container">
|
|
4
|
+
<div v-if="error" class="error-message">
|
|
5
|
+
{{ error }}
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div v-if="stripe && clientSecret" class="payment-form">
|
|
9
|
+
<form @submit.prevent="handleSubmit">
|
|
10
|
+
<div ref="paymentElement"></div>
|
|
11
|
+
|
|
12
|
+
<button type="submit" class="payment-button" :disabled="!stripe || loading">
|
|
13
|
+
<span v-if="loading">Processing...</span>
|
|
14
|
+
<span v-else>Pay Now</span>
|
|
15
|
+
</button>
|
|
16
|
+
</form>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import {
|
|
23
|
+
ref,
|
|
24
|
+
onMounted
|
|
25
|
+
} from 'vue'
|
|
26
|
+
import type {
|
|
27
|
+
Stripe,
|
|
28
|
+
StripeElements
|
|
29
|
+
} from '@stripe/stripe-js'
|
|
30
|
+
import {
|
|
31
|
+
loadStripe
|
|
32
|
+
} from '@stripe/stripe-js'
|
|
33
|
+
|
|
34
|
+
const props = defineProps < {
|
|
35
|
+
orderCode: string,
|
|
36
|
+
clientSecret: string
|
|
37
|
+
} > ()
|
|
38
|
+
|
|
39
|
+
const stripe = ref < Stripe | null > (null)
|
|
40
|
+
const elements = ref < StripeElements | null > (null)
|
|
41
|
+
const error = ref < string | null > (null)
|
|
42
|
+
const loading = ref(false)
|
|
43
|
+
const paymentElement = ref < HTMLDivElement | null > (null)
|
|
44
|
+
const clientSecret = ref < string > (props.clientSecret)
|
|
45
|
+
|
|
46
|
+
onMounted(async () => {
|
|
47
|
+
try {
|
|
48
|
+
stripe.value = await loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY)
|
|
49
|
+
if (stripe.value && clientSecret.value) {
|
|
50
|
+
elements.value = stripe.value.elements({
|
|
51
|
+
clientSecret: clientSecret.value,
|
|
52
|
+
appearance: {
|
|
53
|
+
theme: 'stripe',
|
|
54
|
+
variables: {
|
|
55
|
+
colorPrimary: '#0570de',
|
|
56
|
+
colorBackground: '#ffffff',
|
|
57
|
+
colorText: '#30313d'
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// Create and mount payment element
|
|
63
|
+
if (elements.value && paymentElement.value) {
|
|
64
|
+
const element = elements.value.create('payment')
|
|
65
|
+
element.mount(paymentElement.value as HTMLDivElement)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch (err) {
|
|
69
|
+
error.value = 'Failed to initialize Stripe'
|
|
70
|
+
console.error('Stripe initialization error:', err)
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const handleSubmit = async () => {
|
|
75
|
+
if (!stripe.value || !elements.value) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
loading.value = true
|
|
81
|
+
error.value = null
|
|
82
|
+
|
|
83
|
+
const {
|
|
84
|
+
error: stripeError
|
|
85
|
+
} = await stripe.value.confirmPayment({
|
|
86
|
+
elements: elements.value,
|
|
87
|
+
confirmParams: {
|
|
88
|
+
return_url: `${window.location.origin}/checkout/confirmation/${props.orderCode}`,
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
if (stripeError) {
|
|
93
|
+
error.value = stripeError.message || 'Payment failed'
|
|
94
|
+
}
|
|
95
|
+
} catch (err) {
|
|
96
|
+
error.value = 'An unexpected error occurred'
|
|
97
|
+
console.error('Payment error:', err)
|
|
98
|
+
} finally {
|
|
99
|
+
loading.value = false
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<style scoped>
|
|
105
|
+
.stripe-payment-container {
|
|
106
|
+
max-width: 500px;
|
|
107
|
+
margin: 0 auto;
|
|
108
|
+
padding: 20px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.error-message {
|
|
112
|
+
color: #df1b41;
|
|
113
|
+
background-color: #fff0f0;
|
|
114
|
+
padding: 12px;
|
|
115
|
+
border-radius: 4px;
|
|
116
|
+
margin-bottom: 16px;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.payment-form {
|
|
120
|
+
background: #ffffff;
|
|
121
|
+
padding: 20px;
|
|
122
|
+
border-radius: 4px;
|
|
123
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.payment-button {
|
|
127
|
+
background: #5469d4;
|
|
128
|
+
color: #ffffff;
|
|
129
|
+
border: none;
|
|
130
|
+
border-radius: 4px;
|
|
131
|
+
padding: 12px 16px;
|
|
132
|
+
font-size: 16px;
|
|
133
|
+
font-weight: 600;
|
|
134
|
+
cursor: pointer;
|
|
135
|
+
display: block;
|
|
136
|
+
width: 100%;
|
|
137
|
+
margin-top: 24px;
|
|
138
|
+
transition: all 0.2s ease;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.payment-button:disabled {
|
|
142
|
+
opacity: 0.5;
|
|
143
|
+
cursor: not-allowed;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.payment-button:hover:not(:disabled) {
|
|
147
|
+
filter: brightness(1.1);
|
|
148
|
+
}
|
|
149
|
+
</style>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col gap-2">
|
|
3
|
+
<v-btn size="lg" class="w-full xs:ml-4" :disabled="loading || hasItemInCart" @click="addToCart">
|
|
4
|
+
<template #prefix>
|
|
5
|
+
<SfIconShoppingCart size="sm" />
|
|
6
|
+
</template>
|
|
7
|
+
Add to Cart
|
|
8
|
+
</v-btn>
|
|
9
|
+
<compareBtn />
|
|
10
|
+
</div>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup>
|
|
14
|
+
import { SfIconShoppingCart } from "@storefront-ui/vue";
|
|
15
|
+
import compareBtn from './compareBtn.vue';
|
|
16
|
+
import { useCartStore } from '~/stores/cart'
|
|
17
|
+
|
|
18
|
+
const props = defineProps({
|
|
19
|
+
product: {
|
|
20
|
+
type: Object,
|
|
21
|
+
required: true
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const cart = useCartStore()
|
|
26
|
+
|
|
27
|
+
const addToCart = async (product) => {
|
|
28
|
+
try {
|
|
29
|
+
await cart.addItem({
|
|
30
|
+
sku: product.sku,
|
|
31
|
+
name: product.name,
|
|
32
|
+
price: product.price,
|
|
33
|
+
qty: 1
|
|
34
|
+
})
|
|
35
|
+
} catch (error) {
|
|
36
|
+
// Handle error (show notification, etc.)
|
|
37
|
+
console.error('Failed to add item to cart:', error)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
</script>
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex items-center py-4 border-b border-gray-200">
|
|
3
|
+
<div class="shrink-0 w-24 h-24">
|
|
4
|
+
<productCard :product="productForCard" />
|
|
5
|
+
</div>
|
|
6
|
+
<div class="ml-4 flex-1">
|
|
7
|
+
<h3 class="text-lg font-medium text-gray-900">{{ item.productVariant?.name }}</h3>
|
|
8
|
+
<p class="mt-1 text-sm text-gray-500">SKU: {{ item.productVariant?.sku }}</p>
|
|
9
|
+
<div class="mt-2 flex items-center">
|
|
10
|
+
<div class="flex items-center border border-gray-300 rounded">
|
|
11
|
+
<button
|
|
12
|
+
class="px-2 py-1 text-gray-600 hover:bg-gray-100"
|
|
13
|
+
@click="updateQuantity(item.id, item.quantity - 1)"
|
|
14
|
+
:disabled="item.quantity <= 1"
|
|
15
|
+
>
|
|
16
|
+
-
|
|
17
|
+
</button>
|
|
18
|
+
<span class="px-4 py-1">{{ item.quantity }}</span>
|
|
19
|
+
<button
|
|
20
|
+
class="px-2 py-1 text-gray-600 hover:bg-gray-100"
|
|
21
|
+
@click="updateQuantity(item.id, item.quantity + 1)"
|
|
22
|
+
>
|
|
23
|
+
+
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
<button
|
|
27
|
+
class="ml-4 text-sm text-red-600 hover:text-red-800"
|
|
28
|
+
@click="removeItem(item.id)"
|
|
29
|
+
>
|
|
30
|
+
Remove
|
|
31
|
+
</button>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="ml-4 text-right">
|
|
35
|
+
<p class="text-lg font-medium text-gray-900">
|
|
36
|
+
{{ formatPrice(item.unitPriceWithTax || productForCardPrice) }}
|
|
37
|
+
</p>
|
|
38
|
+
<p v-if="item.listPriceWithTax && item.listPriceWithTax !== item.unitPriceWithTax" class="mt-1 text-sm text-gray-500 line-through">
|
|
39
|
+
{{ formatPrice(item.listPriceWithTax) }}
|
|
40
|
+
</p>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<script setup lang="ts">
|
|
46
|
+
import productCard from '../catalog/product/productCard.vue';
|
|
47
|
+
import { computed } from 'vue';
|
|
48
|
+
|
|
49
|
+
const props = defineProps<{ item: Record<string, any> }>();
|
|
50
|
+
const emit = defineEmits(['cart-changed'])
|
|
51
|
+
const nuxtApp = useNuxtApp()
|
|
52
|
+
|
|
53
|
+
// Prepare a product object compatible with productCard.vue
|
|
54
|
+
const productForCard = computed(() => {
|
|
55
|
+
const pv = props.item?.productVariant || {};
|
|
56
|
+
return {
|
|
57
|
+
id: pv?.product?.id || pv?.id || props.item?.id,
|
|
58
|
+
name: pv?.name || pv?.product?.name,
|
|
59
|
+
image: pv?.featuredAsset ? { filename_disk: pv.featuredAsset?.id } : (pv?.product?.image || {}),
|
|
60
|
+
brands: pv?.product?.brands || [],
|
|
61
|
+
currency: pv?.product?.currency || [],
|
|
62
|
+
price: (props.item?.unitPriceWithTax && props.item.unitPriceWithTax / 100) || pv?.price || 0,
|
|
63
|
+
rating: pv?.product?.rating || 0,
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const productForCardPrice = computed(() => productForCard.value?.price || 0);
|
|
68
|
+
|
|
69
|
+
async function removeItem(orderLineId: string) {
|
|
70
|
+
try {
|
|
71
|
+
// Try to delete using Directus client
|
|
72
|
+
try {
|
|
73
|
+
if (nuxtApp.$directus && nuxtApp.$directus.items) {
|
|
74
|
+
await nuxtApp.$directus.items('order_lines').delete(orderLineId);
|
|
75
|
+
} else if (nuxtApp.$deleteItem) {
|
|
76
|
+
await nuxtApp.$deleteItem('order_lines', orderLineId);
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {
|
|
79
|
+
console.warn('Directus delete failed, falling back to plugin helper or ignoring', e);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
emit('cart-changed');
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error('Failed to remove item:', error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function updateQuantity(orderLineId: string, newQuantity: number) {
|
|
89
|
+
if (newQuantity < 1) return;
|
|
90
|
+
try {
|
|
91
|
+
// Try to update via Directus SDK
|
|
92
|
+
try {
|
|
93
|
+
if (nuxtApp.$directus && nuxtApp.$directus.items) {
|
|
94
|
+
await nuxtApp.$directus.items('order_lines').update(orderLineId, { quantity: newQuantity });
|
|
95
|
+
} else if (nuxtApp.$createItem) {
|
|
96
|
+
// Fallback: read existing, delete and recreate with new quantity
|
|
97
|
+
const existing = await nuxtApp.$readItem('order_lines', orderLineId).catch(() => null);
|
|
98
|
+
if (existing) {
|
|
99
|
+
await nuxtApp.$deleteItem('order_lines', orderLineId).catch(() => null);
|
|
100
|
+
const payload = { ...existing, quantity: newQuantity };
|
|
101
|
+
delete payload.id;
|
|
102
|
+
await nuxtApp.$createItem('order_lines', payload).catch(() => null);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
console.warn('Directus update fallback failed', e);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
emit('cart-changed');
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error('Failed to update quantity:', error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function formatPrice(price: number) {
|
|
116
|
+
const p = price ?? 0;
|
|
117
|
+
// If price seems to be in cents (large integer), convert
|
|
118
|
+
const normalized = p > 1000 ? p / 100 : p;
|
|
119
|
+
return new Intl.NumberFormat('en-US', {
|
|
120
|
+
style: 'currency',
|
|
121
|
+
currency: 'USD'
|
|
122
|
+
}).format(normalized);
|
|
123
|
+
}
|
|
124
|
+
</script>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<v-btn color="primary" @click="redirectToCheckout">
|
|
4
|
+
Proceed to Payment
|
|
5
|
+
</v-btn>
|
|
6
|
+
</div>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup>
|
|
10
|
+
import getActiveOrderQuery from '#graphql/app/commerce/queries/activeOrder.gql';
|
|
11
|
+
|
|
12
|
+
const nuxtApp = useNuxtApp();
|
|
13
|
+
|
|
14
|
+
const redirectToCheckout = async () => {
|
|
15
|
+
try {
|
|
16
|
+
// Fetch the active order to get the order code or id
|
|
17
|
+
const orderRes = await nuxtApp.$graphql.request(getActiveOrderQuery);
|
|
18
|
+
const order = orderRes?.activeOrder;
|
|
19
|
+
if (!order) return;
|
|
20
|
+
|
|
21
|
+
// Attempt to create a checkout session in Directus
|
|
22
|
+
try {
|
|
23
|
+
const payload = {
|
|
24
|
+
orderCode: order.code,
|
|
25
|
+
items: (order.lines || []).map((l) => ({ sku: l?.productVariant?.sku, quantity: l.quantity }))
|
|
26
|
+
};
|
|
27
|
+
const created = await nuxtApp.$createItem('checkout_sessions', payload);
|
|
28
|
+
// support common response shapes
|
|
29
|
+
const redirectUrl = created?.redirect_url || created?.data?.redirect_url || created?.url || created?.data?.url;
|
|
30
|
+
if (redirectUrl) {
|
|
31
|
+
window.location.href = redirectUrl;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
} catch (e) {
|
|
35
|
+
console.warn('Directus checkout session creation failed, falling back:', e);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Fallback: redirect to local confirmation route
|
|
39
|
+
window.location.href = `/checkout/confirmation/${order.code}`;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('Checkout redirect failed:', error);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
</script>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<SfButton size="sm" variant="tertiary" @click="handleCompare" :disabled="isInCompare">
|
|
3
|
+
<template #prefix>
|
|
4
|
+
<SfIconCompareArrows size="sm" />
|
|
5
|
+
</template>
|
|
6
|
+
{{ buttonText }}
|
|
7
|
+
</SfButton>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
import { computed } from 'vue';
|
|
12
|
+
import { useCompareStore } from '../../stores/compare';
|
|
13
|
+
import { useNuxtApp } from '#imports';
|
|
14
|
+
import type { Product } from '../../types/product';
|
|
15
|
+
|
|
16
|
+
// Define props
|
|
17
|
+
const props = defineProps<{ product: Product }>();
|
|
18
|
+
|
|
19
|
+
const compareStore = useCompareStore();
|
|
20
|
+
const { $directus } = useNuxtApp() as any;
|
|
21
|
+
|
|
22
|
+
// Check if the product is already in compare list
|
|
23
|
+
const isInCompare = computed(() => {
|
|
24
|
+
return compareStore.getComparedProducts.some((product: { sku: string; }) => product.sku === props.product?.sku) || compareStore.getComparedProductSkus.includes(props.product?.sku);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Dynamically update button text
|
|
28
|
+
const buttonText = computed(() => (isInCompare.value ? 'In Compare List' : 'Add to Compare'));
|
|
29
|
+
|
|
30
|
+
// Handle Add/Remove from Compare list using Directus where possible
|
|
31
|
+
const handleCompare = async () => {
|
|
32
|
+
try {
|
|
33
|
+
if (!props.product || !props.product.sku) {
|
|
34
|
+
throw new Error('Product data is required');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const sku = props.product.sku;
|
|
38
|
+
|
|
39
|
+
if (isInCompare.value) {
|
|
40
|
+
// Remove from Directus compare_items collection if exists
|
|
41
|
+
try {
|
|
42
|
+
const itemsRes = await $directus.$readItems('compare_items', { filter: { sku: { _eq: sku } } });
|
|
43
|
+
const items = itemsRes?.data || itemsRes || [];
|
|
44
|
+
for (const it of items) {
|
|
45
|
+
const id = it.id || it._id || it.ID;
|
|
46
|
+
if (id) await $directus.$deleteItem('compare_items', id);
|
|
47
|
+
}
|
|
48
|
+
} catch (e) {
|
|
49
|
+
// ignore Directus errors, still update local store
|
|
50
|
+
console.warn('Directus remove compare item failed:', e);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
compareStore.removeComparedProduct(sku);
|
|
54
|
+
} else {
|
|
55
|
+
// Add to Directus compare_items collection
|
|
56
|
+
try {
|
|
57
|
+
await $directus.$createItem('compare_items', { sku });
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.warn('Directus create compare item failed:', e);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
compareStore.addComparedProductSku(sku);
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('Error handling compare:', error);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
</script>
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="currency-selector">
|
|
3
|
+
<select v-model="selectedCurrency" @change="handleCurrencyChange" class="currency-select">
|
|
4
|
+
<option v-for="currency in availableCurrencies" :key="currency.code" :value="currency.code">
|
|
5
|
+
{{ currency.symbol }} {{ currency.code }}
|
|
6
|
+
</option>
|
|
7
|
+
</select>
|
|
8
|
+
</div>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup>
|
|
12
|
+
import {
|
|
13
|
+
ref,
|
|
14
|
+
onMounted,
|
|
15
|
+
watch
|
|
16
|
+
} from 'vue'
|
|
17
|
+
import {
|
|
18
|
+
useCurrencyStore
|
|
19
|
+
} from '~/stores/currency'
|
|
20
|
+
import {
|
|
21
|
+
useQuery,
|
|
22
|
+
useMutation
|
|
23
|
+
} from '@vue/apollo-composable'
|
|
24
|
+
import {
|
|
25
|
+
CURRENCY_QUERY
|
|
26
|
+
} from '#graphql/commerce/queries/currency.gql'
|
|
27
|
+
import {
|
|
28
|
+
UPDATE_USER_CURRENCY
|
|
29
|
+
} from '#graphql/commerce/mutations/updateUserCurrency.gql'
|
|
30
|
+
import {
|
|
31
|
+
getCurrencySymbol
|
|
32
|
+
} from '~/utils/currency'
|
|
33
|
+
import {
|
|
34
|
+
useCurrency
|
|
35
|
+
} from '~/app/composables/useCurrency'
|
|
36
|
+
|
|
37
|
+
const store = useCurrencyStore()
|
|
38
|
+
const selectedCurrency = ref('')
|
|
39
|
+
const availableCurrencies = ref([])
|
|
40
|
+
|
|
41
|
+
const {
|
|
42
|
+
currentCurrency,
|
|
43
|
+
setCurrency
|
|
44
|
+
} = useCurrency()
|
|
45
|
+
|
|
46
|
+
// Fetch currency data from Magento
|
|
47
|
+
const {
|
|
48
|
+
result: currencyResult
|
|
49
|
+
} = useQuery(CURRENCY_QUERY)
|
|
50
|
+
|
|
51
|
+
// Mutation for updating user currency preference
|
|
52
|
+
const {
|
|
53
|
+
mutate: updateUserCurrency
|
|
54
|
+
} = useMutation(UPDATE_USER_CURRENCY)
|
|
55
|
+
|
|
56
|
+
// Watch for currency data changes
|
|
57
|
+
watch(currencyResult, (newResult) => {
|
|
58
|
+
if (newResult?.currency) {
|
|
59
|
+
const {
|
|
60
|
+
available_currency_codes,
|
|
61
|
+
exchange_rates,
|
|
62
|
+
default_display_currency_code
|
|
63
|
+
} = newResult.currency
|
|
64
|
+
|
|
65
|
+
// Map available currencies with their symbols
|
|
66
|
+
availableCurrencies.value = available_currency_codes.map(code => ({
|
|
67
|
+
code,
|
|
68
|
+
symbol: getCurrencySymbol(code),
|
|
69
|
+
rate: exchange_rates.find(rate => rate.currency_to === code)?.rate || 1
|
|
70
|
+
}))
|
|
71
|
+
|
|
72
|
+
// Set initial currency from user preference or default
|
|
73
|
+
const userCurrency = store.getCurrentCurrency
|
|
74
|
+
selectedCurrency.value = userCurrency || default_display_currency_code
|
|
75
|
+
|
|
76
|
+
// Update exchange rates in store
|
|
77
|
+
const ratesObj = {}
|
|
78
|
+
exchange_rates.forEach(({
|
|
79
|
+
currency_to,
|
|
80
|
+
rate
|
|
81
|
+
}) => {
|
|
82
|
+
ratesObj[currency_to] = rate
|
|
83
|
+
})
|
|
84
|
+
store.setExchangeRates(ratesObj)
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// Handle currency change
|
|
89
|
+
const handleCurrencyChange = async () => {
|
|
90
|
+
await setCurrency(selectedCurrency.value)
|
|
91
|
+
|
|
92
|
+
// If user is logged in, save preference
|
|
93
|
+
if (store.user) {
|
|
94
|
+
try {
|
|
95
|
+
await updateUserCurrency({
|
|
96
|
+
variables: {
|
|
97
|
+
currency: selectedCurrency.value
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error('Failed to update user currency preference:', error)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
onMounted(() => {
|
|
107
|
+
// Initialize with store currency if available
|
|
108
|
+
if (currentCurrency.value) {
|
|
109
|
+
selectedCurrency.value = currentCurrency.value
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
</script>
|
|
113
|
+
|
|
114
|
+
<style scoped>
|
|
115
|
+
.currency-selector {
|
|
116
|
+
display: inline-block;
|
|
117
|
+
margin: 0 10px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.currency-select {
|
|
121
|
+
padding: 5px 10px;
|
|
122
|
+
border: 1px solid #ddd;
|
|
123
|
+
border-radius: 4px;
|
|
124
|
+
background-color: white;
|
|
125
|
+
cursor: pointer;
|
|
126
|
+
font-size: 14px;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.currency-select:focus {
|
|
130
|
+
outline: none;
|
|
131
|
+
border-color: #007bff;
|
|
132
|
+
}
|
|
133
|
+
</style>
|