Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 135 additions & 123 deletions client-app/pages/product.vue
Original file line number Diff line number Diff line change
@@ -1,138 +1,147 @@
<template>
<VcContainer
v-if="product && productTemplate"
ref="productComponentAnchor"
class="print:min-w-[1024px] print:bg-transparent print:px-0 print:[zoom:0.7]"
>
<FiltersPopupSidebar
:is-exist-selected-facets="hasSelectedFacets"
:popup-sidebar-filters="productsFilters"
:facets-loading="fetchingFacets"
:is-mobile="isMobile"
:is-visible="isFiltersSidebarVisible"
:loading="fetchingVariations"
:hide-controls="false"
@hide-popup-sidebar="hideFiltersSidebar"
@reset-facet-filters="resetFacetFilters"
@apply-filters="applyFilters"
/>

<!-- Breadcrumbs -->
<VcBreadcrumbs class="mb-3" :items="breadcrumbs" />

<VcTypography tag="h1">
{{ selectedVariationName || product.name }}
</VcTypography>

<div class="mt-2 flex flex-wrap gap-1 max-sm:justify-between sm:gap-6">
<VcCopyText
v-if="!product.hasVariations"
:text="product.code"
:notification="$t('pages.product.sku_copied_message')"
>
<span class="text-base text-secondary-900">
{{ $t("pages.product.sku_label") }}
<span class="font-black">#{{ product.code }}</span>
</span>
</VcCopyText>

<ProductRating v-if="productReviewsEnabled && product.rating" :rating="product.rating" />
</div>

<VcLayout sidebar-position="right" sticky-sidebar class="mt-5">
<div class="space-y-5 xl:space-y-6">
<component
:is="productInfoSection?.type"
v-if="productInfoSection && !productInfoSection.hidden"
:product="product"
:variations="variations"
:model="productInfoSection"
:fetching-variations="fetchingVariations"
/>
<template v-if="product && productTemplate">
<VcContainer
ref="productComponentAnchor"
class="print:min-w-[1024px] print:bg-transparent print:px-0 print:[zoom:0.7]"
>
<FiltersPopupSidebar
:is-exist-selected-facets="hasSelectedFacets"
:popup-sidebar-filters="productsFilters"
:facets-loading="fetchingFacets"
:is-mobile="isMobile"
:is-visible="isFiltersSidebarVisible"
:loading="fetchingVariations"
:hide-controls="false"
@hide-popup-sidebar="hideFiltersSidebar"
@reset-facet-filters="resetFacetFilters"
@apply-filters="applyFilters"
/>

<component
:is="productDescriptionSection && 'type' in productDescriptionSection ? productDescriptionSection.type : ''"
v-if="productDescriptionSection && !productDescriptionSection.hidden"
:product="product"
:model="productDescriptionSection"
:is-collapsible="false"
/>
<!-- Breadcrumbs -->
<VcBreadcrumbs class="mb-3" :items="breadcrumbs" />

<VcTypography tag="h1">
{{ selectedVariationName || product.name }}
</VcTypography>

<div class="mt-2 flex flex-wrap gap-1 max-sm:justify-between sm:gap-6">
<VcCopyText
v-if="!product.hasVariations"
:text="product.code"
:notification="$t('pages.product.sku_copied_message')"
>
<span class="text-base text-secondary-900">
{{ $t("pages.product.sku_label") }}
<span class="font-black">#{{ product.code }}</span>
</span>
</VcCopyText>

<ProductRating v-if="productReviewsEnabled && product.rating" :rating="product.rating" />
</div>

<ProductConfiguration
v-if="product.isConfigurable && configuration?.length"
:product-id="productId"
:configuration="configuration"
:initial-configuration="initialConfiguration"
/>
<VcLayout sidebar-position="right" sticky-sidebar class="mt-5">
<div class="space-y-5 xl:space-y-6">
<component
:is="productInfoSection?.type"
v-if="productInfoSection && !productInfoSection.hidden"
:product="product"
:variations="variations"
:model="productInfoSection"
:fetching-variations="fetchingVariations"
/>

<component
:is="productDescriptionSection && 'type' in productDescriptionSection ? productDescriptionSection.type : ''"
v-if="productDescriptionSection && !productDescriptionSection.hidden"
:product="product"
:model="productDescriptionSection"
:is-collapsible="false"
/>

<KeepAlive>
<ProductReviews
v-if="productReviewsEnabled && !productReviewsSection?.hidden"
<ProductConfiguration
v-if="product.isConfigurable && configuration?.length"
:product-id="productId"
:product-rating="product.rating"
:configuration="configuration"
:initial-configuration="initialConfiguration"
/>
</KeepAlive>

<component
:is="productVariationsBlock?.type"
v-if="productVariationsBlock && !productVariationsBlock.hidden && product.hasVariations"
:variations="variations"
:sort="variationSortInfo"
:model="productVariationsBlock"
:fetching-variations="fetchingVariations"
:page-number="variationsSearchParams.page"
:pages-count="variationsPagesCount"
:products-filters="productsFilters"
:has-selected-filters="hasSelectedFacets"
:product-id="productId"
:product-name="product.name"
@apply-sorting="sortVariations"
@change-page="changeVariationsPage"
@show-filters="showFiltersSidebar"
@reset-filters="resetFacetFilters"
@apply-filters="applyFilters"
/>
</div>
<KeepAlive>
<ProductReviews
v-if="productReviewsEnabled && !productReviewsSection?.hidden"
:product-id="productId"
:product-rating="product.rating"
/>
</KeepAlive>

<component
:is="productVariationsBlock?.type"
v-if="productVariationsBlock && !productVariationsBlock.hidden && product.hasVariations"
:variations="variations"
:sort="variationSortInfo"
:model="productVariationsBlock"
:fetching-variations="fetchingVariations"
:page-number="variationsSearchParams.page"
:pages-count="variationsPagesCount"
:products-filters="productsFilters"
:has-selected-filters="hasSelectedFacets"
:product-id="productId"
:product-name="product.name"
@apply-sorting="sortVariations"
@change-page="changeVariationsPage"
@show-filters="showFiltersSidebar"
@reset-filters="resetFacetFilters"
@apply-filters="applyFilters"
/>
</div>

<template #sidebar>
<ProductPriceBlock
:product="product"
:class="['max-md:mt-5', { 'print:hidden': product.hasVariations }]"
:is-mobile="isMobile"
:template-layout="templateLayout"
:variations="variations"
/>

<template #sidebar>
<ProductSidebar
:class="['max-md:mt-5', { 'print:hidden': product.hasVariations }]"
:product="product"
:variations="variations"
:template-layout="templateLayout"
/>
<ProductVendor :class="['mt-5', { 'print:hidden': product.hasVariations }]" :product="product" />

<ProductPickupLocations
v-if="xPickupEnabled && pickupLocations?.length > 0"
:loading="pickupLocationsLoading"
:pickup-locations="pickupLocations"
class="mt-5"
/>
</template>
</VcLayout>

<ProductPickupLocations
v-if="xPickupEnabled && pickupLocations?.length > 0"
:loading="pickupLocationsLoading"
:pickup-locations="pickupLocations"
/>
</template>
</VcLayout>

<component
:is="relatedProductsSection?.type"
v-if="relatedProductsSection && !relatedProductsSection.hidden"
:related-products="relatedProducts"
:product-id="productId"
:product-name="product.name"
class="mt-5 xl:mt-6"
/>

<template v-if="recommendedProductsSection && !recommendedProductsSection.hidden">
<component
:is="recommendedProductsSection?.type"
v-for="{ model, id } in recommendedProductsSection.blocks"
:key="id"
:recommended-products="recommendedProducts[model as string]"
:title="$t(`pages.product.recommended_products.${model}_section_title`)"
:model="model"
:is="relatedProductsSection?.type"
v-if="relatedProductsSection && !relatedProductsSection.hidden"
:related-products="relatedProducts"
:product-id="productId"
:product-name="product.name"
class="mt-5 xl:mt-6"
/>
</template>
</VcContainer>

<template v-if="recommendedProductsSection && !recommendedProductsSection.hidden">
<component
:is="recommendedProductsSection?.type"
v-for="{ model, id } in recommendedProductsSection.blocks"
:key="id"
:recommended-products="recommendedProducts[model as string]"
:title="$t(`pages.product.recommended_products.${model}_section_title`)"
:model="model"
:product-id="productId"
:product-name="product.name"
class="mt-5 xl:mt-6"
/>
</template>
</VcContainer>

<FloatingBar v-if="isMobile">
<ProductPrice :product="product" :variations="variations" :template-layout="templateLayout" />
</FloatingBar>
</template>

<Error404 v-else-if="!fetchingProduct && productTemplate" />
</template>
Expand Down Expand Up @@ -174,7 +183,8 @@ import { useShortCart } from "@/shared/cart";
import {
useProduct,
useRelatedProducts,
ProductSidebar,
ProductVendor,
ProductPrice,
ProductConfiguration,
useProducts,
useRecommendedProducts,
Expand All @@ -186,6 +196,7 @@ import {
PRODUCT_VARIATIONS_LAYOUT_PROPERTY_NAME,
PRODUCT_VARIATIONS_LAYOUT_PROPERTY_VALUES,
} from "@/shared/catalog/constants/product";
import { FloatingBar } from "@/shared/common";
import { useXPickup } from "@/shared/x-pickup/composables/useXPickup";
import type { ISortInfo } from "@/core/types";
import type {
Expand All @@ -198,6 +209,7 @@ import type { IPageTemplate } from "@/shared/static-content";
import ProductRating from "@/modules/customer-reviews/components/product-rating.vue";
import FiltersPopupSidebar from "@/shared/catalog/components/category/filters-popup-sidebar.vue";
import ProductPickupLocations from "@/shared/catalog/components/product-pickup-locations.vue";
import ProductPriceBlock from "@/shared/catalog/components/product-price-block.vue";

const props = withDefaults(defineProps<IProps>(), {
productId: "",
Expand All @@ -218,7 +230,7 @@ const configurationId = getUrlSearchParam(CONFIGURATION_URL_SEARCH_PARAM);
const lineItemId = getUrlSearchParam(LINE_ITEM_ID_URL_SEARCH_PARAM);

const breakpoints = useBreakpoints(BREAKPOINTS);
const isMobile = breakpoints.smaller("lg");
const isMobile = breakpoints.smaller("md");

const productId = toRef(props, "productId");
const filtersDisplayOrder = toRef(props, "filtersDisplayOrder");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<!-- @deprecated This component is deprecated and should not be used -->
<template>
<div class="rounded border border-transparent bg-additional-50 lg:p-2 lg:hover:border-neutral-100 lg:hover:shadow-lg">
<!-- Product image -->
Expand Down Expand Up @@ -47,4 +48,7 @@ interface IProps {
}

const link = computed<RouteLocationRaw>(() => getProductRoute(props.product.id, props.product.slug));

// eslint-disable-next-line no-console
console.warn("CarouselProductCard is deprecated and should not be used.");
</script>
5 changes: 4 additions & 1 deletion client-app/shared/catalog/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/** @deprecated CarouselProductCard is deprecated and should not be used. */
export { default as CarouselProductCard } from "./carousel-product-card.vue";
export { default as CategorySelector } from "./category-selector.vue";
export { default as CountInCart } from "./count-in-cart.vue";
export { default as DiscountBadge } from "./discount-badge.vue";
export { default as FacetFilter } from "./facet-filter.vue";
export { default as ImageGallery } from "./image-gallery.vue";
export { default as InStock } from "./in-stock.vue";
export { default as Price } from "./price.vue";
export { default as ProductCard } from "./product-card.vue";
export { default as ProductCardCompare } from "./product-card-compare.vue";
// @deprecated Use VcProductCard or ProductCard instead.
Expand All @@ -15,12 +17,13 @@ export { default as ProductCardRecentlyBrowsed } from "./product-card-recently-b
export { default as ProductCardRecommended } from "./product-card-recommended.vue";
export { default as ProductCardRelated } from "./product-card-related.vue";
export { default as ProductConfiguration } from "./configuration/product-configuration.vue";
export { default as ProductPrice } from "./product-price.vue";
export { default as ProductPriceBlock } from "./product-price-block.vue";
export { default as ProductProperty } from "./product-property.vue";
export { default as ProductSidebar } from "./product-sidebar.vue";
export { default as ProductSkeletonGrid } from "./product-skeleton-grid.vue";
export { default as ProductSkeletonList } from "./product-skeleton-list.vue";
export { default as ProductTitledBlock } from "./product-titled-block.vue";
export { default as ProductVendor } from "./product-vendor.vue";
export { default as ProductVideos } from "./product-videos.vue";
export { default as VariationProperty } from "./variation-property.vue";
export { default as Vendor } from "./vendor.vue";
Expand Down
45 changes: 45 additions & 0 deletions client-app/shared/catalog/components/price.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<template>
<div :class="['price', { 'price--loading': loading }]">
<VcLoaderOverlay v-if="loading" />

<VcPriceDisplay v-if="shouldUseActualPrice(value?.list, value?.actual)" class="price__list" :value="value?.list" />

<VcPriceDisplay
v-if="shouldUseActualPrice(value?.list, value?.actual)"
class="price__actual"
:value="value?.actual"
/>

<VcPriceDisplay v-else class="price__value" :value="value?.list" />
</div>
</template>

<script setup lang="ts">
import { shouldUseActualPrice } from "@/ui-kit/utilities/price";
import type { MoneyType, PriceType } from "@/core/api/graphql/types";

interface IProps {
value?: PriceType | { list: MoneyType; actual: MoneyType };
loading?: boolean;
}

defineProps<IProps>();
</script>

<style lang="scss">
.price {
@apply relative flex flex-wrap items-center gap-x-1;

&__list {
@apply mt-0.5 text-xs font-bold text-neutral-400 line-through;
}

&__actual {
@apply font-black text-[--price-color];
}

&__value {
@apply font-bold text-[--price-color];
}
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ defineProps<IProps>();

<style lang="scss">
.pickup-locations {
@apply mt-7;

&__group {
@apply flex flex-row gap-x-3 items-start border rounded p-3;
}
Expand Down
Loading