Files
hossein-por-shop/frontend/components/product/ProductHero.vue
T
marzban-dev bb9c7232d0 Updated
2025-03-17 13:40:58 +03:30

237 lines
8.9 KiB
Vue

<script lang="ts" setup>
// import
import useGetProduct from "~/composables/api/product/useGetProduct";
import { sanitize } from "isomorphic-dompurify";
import type { ProductVariantProvideType } from "~/pages/product/[id].vue";
import useAddCartItem from "~/composables/api/orders/useAddCartItem";
import { useAuth } from "~/composables/api/auth/useAuth";
// state
const route = useRoute();
const id = route.params.id as string | undefined;
const { token } = useAuth();
const { data: product, refetch: refetchProduct } = useGetProduct(id);
const { mutateAsync: addCartItem, isPending: isAddCartItemPending } = useAddCartItem();
const selectedVariantId = ref(product.value!.variants[0].id);
const selectedQuantity = ref(1);
const selectedSlide = ref(product.value!.variants[0].images[0].id);
const selectedColor = ref(product.value!.colors[0]);
// provide / inject
const { selectedVariant, changeSelectedVariant } = inject("productVariant") as ProductVariantProvideType;
// method
const addItemToCart = async () => {
await addCartItem({
id: selectedVariant.value!.id,
quantity: selectedQuantity.value
});
await refetchProduct();
};
// computed
const sanitizedProductDescription = computed(() => {
return sanitize(product.value!.description);
});
// watch
watch(() => selectedVariantId.value, (newId) => {
const newVariant = product.value!.variants.find(variant => variant.id === newId)!;
changeSelectedVariant(newVariant);
});
watch(() => selectedColor.value, (newValue) => {
const filteredVariants = product.value!.variants.filter(v => v.color === newValue);
selectedVariantId.value = filteredVariants[0].id;
selectedVariant.value = filteredVariants[0];
}, {
immediate: true
});
watch(() => selectedVariant.value!, (newValue) => {
selectedQuantity.value = 1;
selectedSlide.value = newValue.images[0].id;
});
</script>
<template>
<div class="flex max-lg:flex-col gap-12 xl:gap-16 container pt-[5rem] pb-28">
<div class="flex flex-col gap-3 lg:hidden">
<NuxtLink to="#" class="typo-label-sm"> {{ product!.category.name }}</NuxtLink>
<h1 class="typo-h-6 xs:typo-h-5 sm:typo-h-4 lg:typo-h-2"> {{ product!.name }} </h1>
<div class="flex w-full items-center justify-between h-[85px]">
<div class="flex items-end gap-4">
<div class="flex flex-col gap-2">
<span
v-if="selectedVariant!.discount > 0"
class="typo-p-lg relative flex-center w-fit"
:class="'after:w-full after:h-[2px] after:bg-black after:absolute'"
>
{{ selectedVariant!.price }}
</span>
<span
class="typo-p-2xl relative flex-center w-fit font-medium"
>
{{ selectedVariant!.discount > 0 ? selectedVariant!.price : selectedVariant!.price }}
</span>
</div>
<div
v-if="selectedVariant!.discount > 0"
class="text-white bg-blue-500 mb-1 px-4 py-2 text-xs rounded-full flex items-center gap-1"
>
<Icon name="material-symbols:percent" class="size-4" />
{{ selectedVariant!.discount }}
درصد تخفیف
</div>
</div>
<Rating :rate="3" />
</div>
</div>
<Slider
class="w-full lg:w-1/2 lg:max-w-[620px]"
v-model:selectedSlide="selectedSlide"
:slides="selectedVariant!.images"
/>
<div class="lg:w-1/2 flex flex-col gap-3 lg:mt-12">
<NuxtLink
to="#"
class="typo-label-sm max-lg:hidden"
>
{{ product!.category.name }}
</NuxtLink>
<h1 class="typo-h-4 xl:typo-h-3 max-lg:hidden"> {{ product!.name }} </h1>
<div class="flex w-full items-center justify-between h-[85px] max-lg:hidden">
<div class="flex items-end gap-4">
<div class="flex flex-col gap-2">
<span
v-if="selectedVariant!.discount > 0"
class="typo-p-lg relative flex-center w-fit"
:class="'after:w-full after:h-[2px] after:bg-black after:absolute'"
>
{{ selectedVariant!.price }}
</span>
<span
class="typo-p-2xl relative flex-center w-fit font-medium"
>
{{ selectedVariant!.discount > 0 ? selectedVariant!.price : selectedVariant!.price }}
</span>
</div>
<div
v-if="selectedVariant!.discount > 0"
class="text-white bg-blue-500 mb-1 px-4 py-2 text-xs rounded-full flex items-center gap-1"
>
<Icon name="material-symbols:percent" class="size-4" />
{{ selectedVariant!.discount }}
<span class="max-sm:hidden">درصد</span>
تخفیف
</div>
<Rating :rate="3" class="sm:hidden" />
</div>
<Rating :rate="3" class="max-sm:hidden" />
</div>
<div
class="py-8 typo-p-md text-slate-500 text-justify [&_a]:text-blue-400 [&_strong]:font-bold [&_u]:text-red-400"
v-html="sanitizedProductDescription"
/>
<div class="flex items-center gap-4">
<span class="typo-p-lg">
تنوع رنگی :
</span>
<div class="flex items-center gap-4 py-4">
<ColorCircle
v-for="color in product!.colors"
:key="color"
@click="selectedColor = color"
selectable
:selected="selectedColor === color "
:style="{backgroundColor: color}"
class="cursor-pointer"
/>
</div>
</div>
<div class="flex items-center gap-6 flex-wrap">
<ProductVariant
@click="variant.in_stock > 0 ? selectedVariantId = variant.id : undefined"
v-for="variant in product!.variants.filter(p => p.color === selectedColor)"
:key="variant.id"
:variantDetail="variant"
:isSelected="selectedVariantId === variant.id"
/>
</div>
<div class="w-full flex flex-col gap-6 mt-10">
<RemainQuantity
:maxQuantity="selectedVariant!.in_stock"
:quantity="selectedQuantity"
/>
<div class="w-full flex gap-3">
<template v-if="token">
<Button
v-if="selectedVariant!.cart_quantity === 0"
@click="addItemToCart"
:loading="isAddCartItemPending"
:disabled="isAddCartItemPending"
class="w-full rounded-full"
end-icon="ci:plus"
>
افزودن به سبد خرید
</Button>
<NuxtLink v-else to="/cart" class="w-full">
<Button
class="w-full rounded-full h-full"
end-icon="ci:cart"
>
مشاهده در سبد خرید
</Button>
</NuxtLink>
</template>
<NuxtLink v-else to="/signin" class="w-full">
<Button
class="w-full rounded-full h-full"
end-icon="ci:user"
>
ابتدا وارد شوید
</Button>
</NuxtLink>
<QuantityCounter
v-if="selectedVariant!.cart_quantity === 0"
:disable="isAddCartItemPending"
v-model="selectedQuantity"
:max="selectedVariant!.in_stock"
/>
<UpdateQuantity v-else />
</div>
<InfoCard />
<Share />
</div>
</div>
</div>
</template>