diff --git a/packages/api-client/src/api/productDetail/productDetailsQuery.ts b/packages/api-client/src/api/productDetail/productDetailsQuery.ts index f4e0b60bb..026b29d53 100644 --- a/packages/api-client/src/api/productDetail/productDetailsQuery.ts +++ b/packages/api-client/src/api/productDetail/productDetailsQuery.ts @@ -30,28 +30,6 @@ export default gql` url_rewrites { url } - price_range { - maximum_price { - final_price { - currency - value - } - regular_price { - currency - value - } - } - minimum_price { - final_price { - currency - value - } - regular_price { - currency - value - } - } - } categories { uid name @@ -73,28 +51,6 @@ export default gql` } } } - price_range { - maximum_price { - final_price { - currency - value - } - regular_price { - currency - value - } - } - minimum_price { - final_price { - currency - value - } - regular_price { - currency - value - } - } - } small_image { url position @@ -134,51 +90,29 @@ export default gql` } options_container special_to_date - ... on BundleProduct { - items { - position - required - sku - title - type - uid - options { - can_change_quantity - is_default - position - uid - quantity - product { - uid - sku - name - price_range { - maximum_price { - final_price { - currency - value - } - regular_price { - currency - value - } - } - minimum_price { - final_price { - currency - value - } - regular_price { - currency - value - } - } - } + ... on ConfigurableProduct { + price_range { + maximum_price { + final_price { + currency + value + } + regular_price { + currency + value + } + } + minimum_price { + final_price { + currency + value + } + regular_price { + currency + value } } } - } - ... on ConfigurableProduct { configurable_options { attribute_code attribute_uid @@ -234,47 +168,7 @@ export default gql` } } } - ... on GroupedProduct { - items { - position - qty - product { - uid - sku - name - stock_status - only_x_left_in_stock - price_range { - maximum_price { - final_price { - currency - value - } - regular_price { - currency - value - } - } - minimum_price { - final_price { - currency - value - } - regular_price { - currency - value - } - } - } - thumbnail { - url - position - disabled - label - } - } - } - } + ... on DownloadableProduct { downloadable_product_samples { sample_url diff --git a/packages/theme/modules/catalog/pages/product.vue b/packages/theme/modules/catalog/pages/product.vue index 806977d80..0b1a6de34 100644 --- a/packages/theme/modules/catalog/pages/product.vue +++ b/packages/theme/modules/catalog/pages/product.vue @@ -36,16 +36,20 @@ import { useRoute, defineComponent, useFetch, + onMounted, } from '@nuxtjs/composition-api'; +import { merge } from 'lodash-es'; import { useCache, CacheTagPrefix } from '@vue-storefront/cache'; import { SfBreadcrumbs, SfLoader } from '@storefront-ui/vue'; import { getBreadcrumbs } from '~/modules/catalog/product/getters/productGetters'; import { useProduct } from '~/modules/catalog/product/composables/useProduct'; import { ProductTypeEnum } from '~/modules/catalog/product/enums/ProductTypeEnum'; import LoadWhenVisible from '~/components/utils/LoadWhenVisible.vue'; - +import { useApi } from '~/composables'; import type { Product } from '~/modules/catalog/product/types'; +import type { ProductDetailsQuery } from '~/modules/GraphQL/types'; import ProductSkeleton from '~/modules/catalog/product/components/ProductSkeleton.vue'; +import getProductPriceBySkuGql from '~/modules/catalog/product/queries/getProductPriceBySku.gql'; export default defineComponent({ name: 'ProductPage', @@ -65,6 +69,7 @@ export default defineComponent({ }, transition: 'fade', setup() { + const { query } = useApi(); const product = ref(null); const { addTags } = useCache(); const { localePath } = useContext(); @@ -97,12 +102,12 @@ export default defineComponent({ // eslint-disable-next-line no-underscore-dangle const renderer = computed(() => product.value?.__typename ?? ProductTypeEnum.SIMPLE_PRODUCT); - const fetchProduct = async (query = getBaseSearchQuery()) => { + const fetchProduct = async (searchQuery = getBaseSearchQuery()) => { const result = await getProductDetails({ - ...query, + ...searchQuery, }); - product.value = result.items[0] as Product ?? null; + product.value = merge({}, product.value, result.items[0] as Product ?? null); }; useFetch(async () => { @@ -126,6 +131,12 @@ export default defineComponent({ addTags([...tags, ...productTags]); }); + onMounted(async () => { + const { data } = await query(getProductPriceBySkuGql, { sku: id }); + + product.value = merge({}, product.value, data.products?.items?.[0] as Product); + }); + return { renderer, loading, diff --git a/packages/theme/modules/catalog/pricing/getGroupedProductPriceCommand.ts b/packages/theme/modules/catalog/pricing/getGroupedProductPriceCommand.ts index 601373a24..fbd69ee17 100644 --- a/packages/theme/modules/catalog/pricing/getGroupedProductPriceCommand.ts +++ b/packages/theme/modules/catalog/pricing/getGroupedProductPriceCommand.ts @@ -8,8 +8,8 @@ export function getGroupedProductPriceCommand(product: GroupedProduct): number { return regular > special && special !== null ? special : regular; }; - return product.items.reduce( + return product.items?.reduce( (acc, curr) => curr.qty * evalProductPrice(curr.product) + acc, 0, - ); + ) ?? 0; } diff --git a/packages/theme/modules/catalog/product/components/product-types/bundle/BundleProduct.vue b/packages/theme/modules/catalog/product/components/product-types/bundle/BundleProduct.vue index 9da480ab3..385220dee 100644 --- a/packages/theme/modules/catalog/product/components/product-types/bundle/BundleProduct.vue +++ b/packages/theme/modules/catalog/product/components/product-types/bundle/BundleProduct.vue @@ -40,17 +40,19 @@
{{ $t('From') }} {{ $t('To') }} - {{ $t('Your customization') }} - +
+ {{ $t('Your customization') }} + +
@@ -89,10 +91,16 @@ class="product__description desktop-only" /> +
+ + + +
props.product?.description?.html || '', ); - const productPrice = ref(getProductPrice(props.product).regular); - const productSpecialPrice = ref(getProductPrice(props.product).special); - const productMaximumPrice = ref(getProductPrice(props.product).maximum); - const customizationPrice = ref(productSpecialPrice.value ?? productPrice.value); - + const productPrices = computed(() => getProductPrice(props.product)); + const customizationPrice = ref(productPrices.value.special ?? productPrices.value.regular); const totalReviews = computed(() => getTotalReviews(props.product)); const averageRating = computed(() => getAverageRating(props.product)); @@ -214,9 +222,7 @@ export default defineComponent({ productGallery, getProductName, getProductSwatchData, - productPrice, - productSpecialPrice, - productMaximumPrice, + productPrices, customizationPrice, productShortDescription, qty, diff --git a/packages/theme/modules/catalog/product/components/product-types/bundle/BundleProductOptionSkeleton.vue b/packages/theme/modules/catalog/product/components/product-types/bundle/BundleProductOptionSkeleton.vue new file mode 100644 index 000000000..ff1d74967 --- /dev/null +++ b/packages/theme/modules/catalog/product/components/product-types/bundle/BundleProductOptionSkeleton.vue @@ -0,0 +1,35 @@ + + diff --git a/packages/theme/modules/catalog/product/components/product-types/configurable/ConfigurableProduct.vue b/packages/theme/modules/catalog/product/components/product-types/configurable/ConfigurableProduct.vue index 25fe0a28d..1117710ea 100644 --- a/packages/theme/modules/catalog/product/components/product-types/configurable/ConfigurableProduct.vue +++ b/packages/theme/modules/catalog/product/components/product-types/configurable/ConfigurableProduct.vue @@ -291,6 +291,7 @@ export default defineComponent({ emit('fetchProduct', { query: getBaseSearchQuery() }); }; + return { addItem, addItemToWishlist: addOrRemoveItem, diff --git a/packages/theme/modules/catalog/product/components/product-types/grouped/GroupedProductSelector.vue b/packages/theme/modules/catalog/product/components/product-types/grouped/GroupedProductSelector.vue index d4b4712a4..59f588ce9 100644 --- a/packages/theme/modules/catalog/product/components/product-types/grouped/GroupedProductSelector.vue +++ b/packages/theme/modules/catalog/product/components/product-types/grouped/GroupedProductSelector.vue @@ -126,8 +126,8 @@ export default defineComponent({ watch( () => props.product, - (product) => { - const price = getGroupedProductPriceCommand(product); + (productNewValue) => { + const price = getGroupedProductPriceCommand(productNewValue); emit('update-price', price); }, { diff --git a/packages/theme/modules/catalog/product/queries/getProductPriceBySku.gql.ts b/packages/theme/modules/catalog/product/queries/getProductPriceBySku.gql.ts new file mode 100644 index 000000000..4235ced34 --- /dev/null +++ b/packages/theme/modules/catalog/product/queries/getProductPriceBySku.gql.ts @@ -0,0 +1,127 @@ +import gql from 'graphql-tag'; + +const fragmentPriceRangeFields = gql` + fragment PriceRangeFields on PriceRange { + maximum_price { + final_price { + currency + value + } + regular_price { + currency + value + } + } + minimum_price { + final_price { + currency + value + } + regular_price { + currency + value + } + } + } +`; + +export default gql` + query getProductPriceBySku($sku: String) { + products(filter: {sku: {eq: $sku}}) { + items { + price_range { + ...PriceRangeFields + } + + ... on BundleProduct { + items { + position + required + sku + title + type + uid + options { + can_change_quantity + is_default + position + uid + quantity + product { + uid + sku + name + price_range { + maximum_price { + final_price { + currency + value + } + regular_price { + currency + value + } + } + minimum_price { + final_price { + currency + value + } + regular_price { + currency + value + } + } + } + } + } + } + } + + ... on GroupedProduct { + items { + position + qty + product { + uid + sku + name + stock_status + only_x_left_in_stock + price_range { + maximum_price { + final_price { + currency + value + } + regular_price { + currency + value + } + } + minimum_price { + final_price { + currency + value + } + regular_price { + currency + value + } + } + } + thumbnail { + url + position + disabled + label + } + } + } + } + + } + } + } + ${fragmentPriceRangeFields} +`;