Merge branch 'main' of https://github.com/Byeto-Company/hossein_por_shop
This commit is contained in:
@@ -76,12 +76,12 @@ const handleDeleteAddress = (id: number) => {
|
||||
</div>
|
||||
<span class="flex items-center justify-between w-full gap-3">
|
||||
<div
|
||||
class="flex items-center gap-3 typo-sub-h-lg font-semibold text-slate-900"
|
||||
class="flex items-center gap-3 lg:text-[1.125rem] font-semibold text-slate-900"
|
||||
>
|
||||
{{ !!address ? address.name : "آدرس" }}
|
||||
<span
|
||||
v-if="isSelected"
|
||||
class="bg-black rounded-xl px-3 py-2 text-slate-200 text-xs"
|
||||
class="bg-black rounded-lg px-3 py-2 text-slate-200 text-[10px] lg:text-xs"
|
||||
>
|
||||
انتخاب شده
|
||||
</span>
|
||||
@@ -97,7 +97,7 @@ const handleDeleteAddress = (id: number) => {
|
||||
</span>
|
||||
|
||||
<div
|
||||
class="flex flex-col items-center justify-between w-full gap-8 lg:flex-row"
|
||||
class="flex flex-col items-center justify-between w-full gap-5 lg:gap-8 lg:flex-row"
|
||||
>
|
||||
<div class="w-full lg:w-9/12 overflow-hidden">
|
||||
<div
|
||||
|
||||
@@ -54,29 +54,27 @@ const handleSubmitDiscountCode = () => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col bg-slate-50 sticky top-44 w-full lg:w-3/12 transition-all border border-slate-200 rounded-xl"
|
||||
class="flex flex-col bg-slate-50 w-full lg:w-3/12 transition-all border border-slate-200 rounded-xl"
|
||||
>
|
||||
<div
|
||||
class="w-full flex items-center justify-between p-5 border-b border-slate-200"
|
||||
class="w-full flex items-center justify-between py-5 px-4 border-b border-slate-200"
|
||||
>
|
||||
<span class="typo-sub-h-xl text-black">فاکتور خرید</span>
|
||||
<Icon name="ci:cart" class="**:stroke-black" size="24" />
|
||||
</div>
|
||||
|
||||
<div v-if="cartIsLoading" class="flex flex-col p-5 gap-4">
|
||||
<div v-if="cartIsLoading" class="flex flex-col p-4 gap-4 !rounded-lg">
|
||||
<Skeleton
|
||||
v-for="i in 5"
|
||||
:key="i"
|
||||
class="w-full !h-7"
|
||||
:class="{
|
||||
'!h-12': [4, 5].includes(i),
|
||||
'!rounded-full': i == 5,
|
||||
'!rounded-lg': i != 5,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else class="flex flex-col p-5 gap-4">
|
||||
<div v-else class="flex flex-col p-4 gap-4">
|
||||
<div
|
||||
class="flex items-center justify-between w-full text-slate-800"
|
||||
>
|
||||
@@ -116,23 +114,29 @@ const handleSubmitDiscountCode = () => {
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
v-model="discountCode"
|
||||
placeholder="کد تخفیف"
|
||||
class="!py-2 !pe-2 ps-2.5"
|
||||
>
|
||||
<template #endItem>
|
||||
<button
|
||||
@click="handleSubmitDiscountCode"
|
||||
class="text-xs px-3 rounded-[7px] py-1.5 text-white bg-black hover:invert border border-white transition-all"
|
||||
>
|
||||
ثبت
|
||||
</button>
|
||||
</template>
|
||||
</Input>
|
||||
<div class="w-full flex justify-between">
|
||||
<Input
|
||||
v-model="discountCode"
|
||||
placeholder="کد تخفیف"
|
||||
class="!py-3 !pe-2 ps-2.5 w-full !rounded-none !border-e-[0px] !rounded-s-100"
|
||||
/>
|
||||
<button
|
||||
@click="handleSubmitDiscountCode"
|
||||
class="text-xs px-5 rounded-e-100 py-1.5 text-white bg-black hover:invert border-[1.5px] border-black hover:border-white transition-all disabled:cursor-not-allowed"
|
||||
:disabled="!discountCode.length"
|
||||
>
|
||||
<Icon
|
||||
v-if="submitDiscountCodeIsPending"
|
||||
name="svg-spinners:180-ring-with-bg"
|
||||
size="20"
|
||||
class="**:fill-white"
|
||||
/>
|
||||
<span v-else> ثبت </span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<NuxtLink :to="{ name: nextPage?.name }">
|
||||
<Button start-icon="bi:arrow-right" class="w-full rounded-full">
|
||||
<Button start-icon="bi:arrow-right" class="w-full rounded-100">
|
||||
{{ nextPage?.label }}
|
||||
</Button>
|
||||
</NuxtLink>
|
||||
|
||||
@@ -1,16 +1,42 @@
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
// imports
|
||||
|
||||
import { useImage } from "@vueuse/core";
|
||||
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
image: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
// props
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const { image } = toRefs(props);
|
||||
|
||||
const { isLoading: cartImageIsLoading } = useImage({
|
||||
src: image.value,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center w-full gap-4">
|
||||
<div
|
||||
v-if="!cartImageIsLoading"
|
||||
class="size-[3.5rem] shrink-0 rounded-100 border border-gray-300 overflow-hidden"
|
||||
>
|
||||
<img src="/img/product-1.jpg" alt="product" class="object-conver" />
|
||||
<img :src="image" alt="product" class="object-conver" />
|
||||
</div>
|
||||
<Skeleton
|
||||
v-else
|
||||
class="!size-[3.5rem] aspect-square shrink-0 !rounded-100 border border-slate-200"
|
||||
/>
|
||||
<span
|
||||
class="text-xs font-semibold lg:text-sm text-gray-800 line-clamp-2"
|
||||
>
|
||||
فشارسنج بازویی امرن Omron M3
|
||||
{{ title }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<script setup lang="ts">
|
||||
// imports
|
||||
|
||||
import useGetCartOrders from "~/composables/api/orders/useGetCartOrders";
|
||||
|
||||
// state
|
||||
|
||||
const showMore = ref(false);
|
||||
|
||||
// queries
|
||||
|
||||
const { data: cart, isLoading: cartIsLoading } = useGetCartOrders();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col items-center w-full gap-4 p-4 border lg:gap-6 border-gray-300 rounded-xl bg-gray-50"
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-start w-full lg:text-[1.125rem] font-semibold text-gray-900"
|
||||
>
|
||||
خلاصه سفارش
|
||||
</span>
|
||||
|
||||
<div
|
||||
class="grid w-full grid-cols-1 gap-4 lg:gap-6 lg:grid-cols-3 md:grid-cols-2 lg:h-max transition-all"
|
||||
:class="showMore ? 'h-[calc(100px)]' : 'h-max'"
|
||||
>
|
||||
<template v-if="cartIsLoading">
|
||||
<Skeleton
|
||||
v-for="i in 6"
|
||||
class="w-full !h-14 !rounded-100"
|
||||
></Skeleton>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="w-full overflow-hidden gap-4 flex flex-col">
|
||||
<MinimalCartItem
|
||||
v-for="(cartItem, index) in cart?.items"
|
||||
:key="index"
|
||||
:image="cartItem.product.image"
|
||||
:title="cartItem.product.title"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="cart?.items.length > 5"
|
||||
class="h-7 flex-center col-span-full lg:hidden"
|
||||
>
|
||||
<button
|
||||
@click="showMore = !showMore"
|
||||
class="gap-2 flex-center"
|
||||
>
|
||||
<span class="text-sm text-black"> مشاهده بیشتر </span>
|
||||
<Icon name="bi:chevron-down" class="**:stroke-black" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -92,7 +92,7 @@ watch(
|
||||
() => debouncedCounter.value,
|
||||
(nv) => {
|
||||
addCartItem(
|
||||
{ id: data.value.id, quantity: nv },
|
||||
{ id: data.value.product.id, quantity: nv },
|
||||
{
|
||||
onSuccess: () => {
|
||||
invalidateCart();
|
||||
@@ -116,20 +116,10 @@ watch(
|
||||
<li
|
||||
class="flex flex-col items-center w-full gap-4 p-4 border lg:flex-row border-slate-200 rounded-xl bg-slate-50 overflow-hidden relative"
|
||||
>
|
||||
<img
|
||||
src="/logo.png"
|
||||
class="absolute -top-5 -left-5 rotate-[135deg] size-28"
|
||||
/>
|
||||
|
||||
<div class="flex items-center justify-start w-full gap-2.5 lg:gap-4">
|
||||
<Skeleton
|
||||
v-if="cartImageIsLoading"
|
||||
class="!size-[12rem] aspect-square shrink-0 !rounded-xl border border-slate-200"
|
||||
/>
|
||||
|
||||
<div class="flex items-start justify-start w-full gap-2.5 lg:gap-4">
|
||||
<div
|
||||
v-else
|
||||
class="size-[12rem] aspect-square shrink-0 rounded-xl border border-slate-200 overflow-hidden"
|
||||
v-if="!cartImageIsLoading"
|
||||
class="size-[4rem] lg:size-[12rem] aspect-square shrink-0 rounded-xl border border-slate-200 overflow-hidden"
|
||||
>
|
||||
<img
|
||||
:src="data.product.image"
|
||||
@@ -137,34 +127,42 @@ watch(
|
||||
alt="product"
|
||||
/>
|
||||
</div>
|
||||
<Skeleton
|
||||
v-else
|
||||
class="!size-[12rem] aspect-square shrink-0 !rounded-xl border border-slate-200"
|
||||
/>
|
||||
|
||||
<div class="flex flex-col w-full gap-4">
|
||||
<span class="font-semibold typo-sub-h-sm text-slate-600">
|
||||
{{ data.product.category }}
|
||||
</span>
|
||||
|
||||
<div class="flex items-center justify-start gap-3">
|
||||
<span class="font-semibold typo-sub-h-xl text-black">
|
||||
{{ data.product.title }}
|
||||
<div class="flex flex-col w-full gap-3 lg:gap-4">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<span
|
||||
class="font-semibold typo-sub-h-xs lg:typo-sub-h-sm text-slate-600"
|
||||
>
|
||||
{{ data.product.category }}
|
||||
</span>
|
||||
<div
|
||||
v-if="data.product.discount > 0"
|
||||
class="text-white bg-blue-500 px-4 py-2 text-xs rounded-full flex items-center gap-1"
|
||||
class="text-white bg-blue-500 px-3 lg:px-4 py-1.5 lg:py-2 text-[10px] lg:text-xs rounded-full flex items-center gap-1"
|
||||
>
|
||||
<Icon name="bi:percent" class="size-4" />
|
||||
{{ data.product.discount }}
|
||||
% تخفیف
|
||||
تخفیف
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span
|
||||
class="font-semibold typo-sub-h-sm lg:typo-sub-h-xl text-black"
|
||||
>
|
||||
{{ data.product.title }}
|
||||
</span>
|
||||
|
||||
<div class="flex items-center justify-start gap-1.5">
|
||||
<div
|
||||
v-if="!!data.product.color"
|
||||
class="px-3 py-1 rounded-full border border-slate-200 text-sm flex-center gap-1.5"
|
||||
class="px-3 py-1 rounded-full border border-slate-200 text-xs lg:text-sm flex-center gap-1.5"
|
||||
>
|
||||
<span> رنگ </span>
|
||||
<span
|
||||
class="!size-4 shadow-black/30 shadow-inner rounded-full"
|
||||
class="size-3 lg:!size-4 shadow-black/30 shadow-inner rounded-full"
|
||||
:style="{
|
||||
backgroundColor: `${data.product.color}`,
|
||||
}"
|
||||
@@ -176,7 +174,7 @@ watch(
|
||||
v-for="(variant, index) in data.product
|
||||
.product_attributes"
|
||||
:index="index"
|
||||
class="px-3 py-1 rounded-full border border-slate-200 text-sm"
|
||||
class="px-3 py-1 rounded-full border border-slate-200 text-xs lg:text-sm"
|
||||
>
|
||||
{{ variant.value }}
|
||||
</span>
|
||||
@@ -249,7 +247,7 @@ watch(
|
||||
<div class="flex items-center">
|
||||
<button
|
||||
@click="handleIncreaseQuantity"
|
||||
class="border size-10 flex-center rounded-100 border-slate-300"
|
||||
class="border size-7 p-1 lg:size-10 flex-center rounded-50 border-slate-300"
|
||||
:class="
|
||||
deleteCartItemIsPending ? 'pointer-events-none' : ''
|
||||
"
|
||||
@@ -257,11 +255,13 @@ watch(
|
||||
<Icon name="bi:plus" class="**:stroke-slate-800" />
|
||||
</button>
|
||||
|
||||
<div class="size-10 text-[1.125rem] flex-center">1</div>
|
||||
<div class="size-10 text-sm flex-center">
|
||||
{{ counter }}
|
||||
</div>
|
||||
|
||||
<button
|
||||
@click="handleDecreaseQuantity"
|
||||
class="border size-10 flex-center rounded-100 border-slate-300"
|
||||
class="border size-7 lg:size-10 p-1 flex-center rounded-50 border-slate-300"
|
||||
:class="
|
||||
deleteCartItemIsPending ? 'pointer-events-none' : ''
|
||||
"
|
||||
@@ -279,7 +279,9 @@ watch(
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<span class="text-[1.125rem] text-slate-900 font-semibold">
|
||||
<span
|
||||
class="text-sm lg:text-[1.125rem] text-slate-900 font-semibold"
|
||||
>
|
||||
{{ data.product.price }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -31,9 +31,6 @@ const handleSubmit = () => {
|
||||
status: "success",
|
||||
},
|
||||
});
|
||||
setTimeout(() => {
|
||||
router.push({ name: "index" });
|
||||
}, 1000);
|
||||
},
|
||||
onError: () => {
|
||||
addToast({
|
||||
@@ -56,7 +53,11 @@ const handleSubmit = () => {
|
||||
@close="isShow = false"
|
||||
>
|
||||
<template #trigger>
|
||||
<Button class="rounded-full" end-icon="bi:trash" size="md">
|
||||
<Button
|
||||
class="rounded-full shrink-0 whitespace-pre"
|
||||
end-icon="bi:trash"
|
||||
size="md"
|
||||
>
|
||||
حذف همه
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
@@ -18,7 +18,7 @@ const { variant, size, loading } = toRefs(props);
|
||||
// computed
|
||||
const classes = computed(() => {
|
||||
return [
|
||||
"flex items-center justify-center transition-all cursor-pointer",
|
||||
"flex items-center justify-center transition-all cursor-pointer max-lg:text-xs",
|
||||
{
|
||||
"btn-solid": variant.value === "solid",
|
||||
"btn-secondary": variant.value === "secondary",
|
||||
|
||||
@@ -1,31 +1,42 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
title: string,
|
||||
products: ProductListItem[]
|
||||
}
|
||||
title?: string;
|
||||
products: ProductListItem[];
|
||||
withHeader?: boolean;
|
||||
};
|
||||
|
||||
// props
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
withHeader: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="w-full flex flex-col gap-10 md:gap-[4rem] py-[5rem] container">
|
||||
<div class="w-full flex justify-between items-center">
|
||||
<span class="text-black typo-h-6 max-sm:text-xl md:typo-h-5 lg:typo-h-4">
|
||||
<section
|
||||
class="w-full flex flex-col gap-10 md:gap-[4rem] py-[5rem] container"
|
||||
>
|
||||
<div v-if="withHeader" class="w-full flex justify-between items-center">
|
||||
<span
|
||||
class="text-black typo-h-6 max-sm:text-xl md:typo-h-5 lg:typo-h-4"
|
||||
>
|
||||
{{ title }}
|
||||
</span>
|
||||
<NuxtLink to="/products">
|
||||
<Button variant="outlined" class="rounded-full max-sm:typo-label-sm max-sm:py-2" end-icon="ci:arrow-left">
|
||||
<Button
|
||||
variant="outlined"
|
||||
class="rounded-full max-sm:typo-label-sm max-sm:py-2"
|
||||
end-icon="ci:arrow-left"
|
||||
>
|
||||
نمایش همه
|
||||
</Button>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-y-8 gap-5 sm:gap-8">
|
||||
<ul
|
||||
class="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-y-8 gap-5 sm:gap-8"
|
||||
>
|
||||
<ProductCard
|
||||
v-for="product in products"
|
||||
:key="product.id"
|
||||
@@ -37,6 +48,6 @@ defineProps<Props>();
|
||||
:rate="product.rating"
|
||||
:dark-layer="true"
|
||||
/>
|
||||
</div>
|
||||
</ul>
|
||||
</section>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -13,48 +13,57 @@ const highlights = ref<Highlight[]>([
|
||||
{
|
||||
icon: "/img/footer-support.svg",
|
||||
title: "خدمات مشتری",
|
||||
description: "پشتیبانی استثنایی، راهحلهای پایدار برای شما"
|
||||
description: "پشتیبانی استثنایی، راهحلهای پایدار برای شما",
|
||||
},
|
||||
{
|
||||
icon: "/img/footer-send.svg",
|
||||
title: "ارسال سریع و رایگان",
|
||||
description: "ارسال رایگان برای سفارشهای بالای ۱۵۰ دلار"
|
||||
description: "ارسال رایگان برای سفارشهای بالای ۱۵۰ دلار",
|
||||
},
|
||||
{
|
||||
icon: "/img/footer-share.svg",
|
||||
title: "معرفی به دوستان",
|
||||
description: "ما را به دوستان خود معرفی کنید"
|
||||
description: "ما را به دوستان خود معرفی کنید",
|
||||
},
|
||||
{
|
||||
icon: "/img/footer-security.svg",
|
||||
title: "پرداخت امن",
|
||||
description: "پرداخت شما بهصورت امن پردازش میشود"
|
||||
}
|
||||
description: "پرداخت شما بهصورت امن پردازش میشود",
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="w-full border-t-[0.5px] border-slate-200">
|
||||
<div class="w-full py-[5rem] gap-12 xs:gap-8 sm:gap-12 xl:gap-0 container grid grid-cols-1 xs:grid-cols-2 lg:grid-cols-4">
|
||||
<div
|
||||
class="w-full py-[5rem] gap-8 sm:gap-12 xl:gap-0 container grid grid-cols-2 lg:grid-cols-4"
|
||||
>
|
||||
<template v-for="(highlight, index) in highlights" :key="index">
|
||||
<div class="flex flex-col-center gap-[.75rem] px-5">
|
||||
|
||||
<img :src="highlight.icon" class="size-[70px] md:size-[90px]" alt="" />
|
||||
<img
|
||||
:src="highlight.icon"
|
||||
class="size-[70px] md:size-[90px]"
|
||||
alt=""
|
||||
/>
|
||||
|
||||
<div class="w-full flex-col-center gap-[.25rem]">
|
||||
<span class="typo-sub-h-md text-black text-center">
|
||||
<span
|
||||
class="typo-sub-h-sm lg:typo-sub-h-md text-black text-center"
|
||||
>
|
||||
{{ highlight.title }}
|
||||
</span>
|
||||
<p class="text-slate-500 typo-p-sm mt-1 text-center">
|
||||
<p
|
||||
class="text-slate-500 typo-p-xs lg:typo-p-sm mt-1 text-center"
|
||||
>
|
||||
{{ highlight.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div-->
|
||||
<!-- class="w-[1px] h-[5rem] bg-slate-200"-->
|
||||
<!-- v-if="index + 1 != highlights.length"-->
|
||||
<!-- />-->
|
||||
<!-- <div-->
|
||||
<!-- class="w-[1px] h-[5rem] bg-slate-200"-->
|
||||
<!-- v-if="index + 1 != highlights.length"-->
|
||||
<!-- />-->
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -43,7 +43,7 @@ const closeSideDrawer = () => {
|
||||
<div
|
||||
@click.stop
|
||||
:class="modelValue ? 'translate-x-0' : 'translate-x-[100%]'"
|
||||
class="md:hidden cursor-default flex top-0 right-0 fixed z-999 transition-all flex-col bg-white w-[350px] h-full gap-8 pt-12"
|
||||
class="md:hidden cursor-default flex top-0 right-0 fixed z-999 transition-all duration-500 rounded-e-xl flex-col bg-white w-[300px] h-full gap-8 pt-12"
|
||||
>
|
||||
<div class="flex items-center flex-col justify-end gap-[1.5rem]">
|
||||
<Tooltip v-if="!!account && !!token" title="حساب کاربری">
|
||||
|
||||
@@ -30,11 +30,10 @@ await suspense();
|
||||
const onSwiper = (swiper: SwiperClass) => {
|
||||
swiper_instance.value = swiper;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="w-full flex flex-col gap-10 md:gap-[4rem] py-[5rem] lg:container">
|
||||
<section class="w-full flex flex-col gap-10 md:gap-[4rem]">
|
||||
<div class="w-full flex justify-between items-center max-lg:container">
|
||||
<span class="text-black typo-h-6 md:typo-h-5 lg:typo-h-4">
|
||||
{{ title }}
|
||||
@@ -92,12 +91,12 @@ const onSwiper = (swiper: SwiperClass) => {
|
||||
:breakpoints="{
|
||||
640: {
|
||||
centeredSlides: true,
|
||||
slidesPerView : 2.5
|
||||
slidesPerView: 2.5,
|
||||
},
|
||||
1024 : {
|
||||
1024: {
|
||||
centeredSlides: false,
|
||||
slidesPerView : 3
|
||||
}
|
||||
slidesPerView: 3,
|
||||
},
|
||||
}"
|
||||
>
|
||||
<SwiperSlide
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
// import
|
||||
|
||||
import Tag from "~/components/global/Tag.vue";
|
||||
@@ -10,7 +9,7 @@ import { useImageColor } from "~/composables/global/useImageColor";
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
id: number,
|
||||
id: number;
|
||||
title: string;
|
||||
colors: string[];
|
||||
price: string;
|
||||
@@ -28,77 +27,89 @@ const { id } = toRefs(props);
|
||||
// state
|
||||
|
||||
const { colorObject } = useImageColor(`#product-image-${id.value}`);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLink :to="'/product/' + id">
|
||||
<div class="@container">
|
||||
<div
|
||||
class="group relative size-full aspect-square rounded-xl @[280px]:rounded-2xl bg-white brightness-[98%] overflow-hidden p-6"
|
||||
>
|
||||
|
||||
<img
|
||||
:id="`product-image-${id}`"
|
||||
:src="picture"
|
||||
class="group-hover:scale-105 transition-transform duration-200 size-full object-contain absolute inset-0"
|
||||
alt="product-background"
|
||||
/>
|
||||
|
||||
<li class="w-full">
|
||||
<NuxtLink :to="'/product/' + id">
|
||||
<div class="@container">
|
||||
<div
|
||||
v-if="darkLayer"
|
||||
class="bg-linear-to-t inset-0 from-black/50 to-transparent to-55% absolute z-10 size-full"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="flex justify-between items-center absolute px-4 @[280px]:px-6 pt-4 @[280px]:pt-6 top-0 w-full inset-x-0"
|
||||
>
|
||||
<Rate v-if="rate" :rate="rate" />
|
||||
<Tag v-if="tag">
|
||||
{{ tag }}
|
||||
</Tag>
|
||||
</div>
|
||||
<div
|
||||
:class="
|
||||
colorObject?.isLight && !darkLayer
|
||||
? 'text-black'
|
||||
: 'text-white'
|
||||
"
|
||||
class="absolute inset-x-0 bottom-0 pb-4 @[280px]:pb-6 px-4 @[280px]:px-6 flex flex-row-reverse justify-between items-end z-10"
|
||||
class="group relative size-full aspect-square rounded-xl @[280px]:rounded-2xl bg-white brightness-[98%] overflow-hidden p-6"
|
||||
>
|
||||
<img
|
||||
:id="`product-image-${id}`"
|
||||
:src="picture"
|
||||
class="group-hover:scale-105 transition-transform duration-200 size-full object-contain absolute inset-0"
|
||||
alt="product-background"
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-2 items-start w-full">
|
||||
<span class="@max-[280px]:hidden typo-sub-h-md @[280px]:typo-sub-h-lg truncate w-full">
|
||||
{{ title }}
|
||||
</span>
|
||||
<div class="flex items-center justify-between w-full mt-1">
|
||||
<div class="flex items-center gap-2 @[280px]:mt-1">
|
||||
<ColorCircle
|
||||
v-for="color in colors"
|
||||
:key="color"
|
||||
:style="{ backgroundColor: color }"
|
||||
class="!size-5 @[280px]:!size-6"
|
||||
/>
|
||||
</div>
|
||||
<span class="@max-[280px]:hidden typo-p-xs @[280px]:typo-p-md !font-semibold whitespace-nowrap">
|
||||
{{ price }}
|
||||
<div
|
||||
v-if="darkLayer"
|
||||
class="bg-linear-to-t inset-0 from-black/50 to-transparent to-55% absolute z-10 size-full"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="flex justify-between items-center absolute px-4 @[280px]:px-6 pt-4 @[280px]:pt-6 top-0 w-full inset-x-0"
|
||||
>
|
||||
<Rate v-if="rate" :rate="rate" />
|
||||
<Tag v-if="tag">
|
||||
{{ tag }}
|
||||
</Tag>
|
||||
</div>
|
||||
<div
|
||||
:class="
|
||||
colorObject?.isLight && !darkLayer
|
||||
? 'text-black'
|
||||
: 'text-white'
|
||||
"
|
||||
class="absolute inset-x-0 bottom-0 pb-4 @[280px]:pb-6 px-4 @[280px]:px-6 flex flex-row-reverse justify-between items-end z-10"
|
||||
>
|
||||
<div class="flex flex-col gap-2 items-start w-full">
|
||||
<span
|
||||
class="@max-[280px]:hidden typo-sub-h-md @[280px]:typo-sub-h-lg truncate w-full"
|
||||
>
|
||||
{{ title }}
|
||||
</span>
|
||||
<div
|
||||
class="flex items-center justify-between w-full mt-1"
|
||||
>
|
||||
<div
|
||||
class="flex items-center gap-2 @[280px]:mt-1"
|
||||
>
|
||||
<ColorCircle
|
||||
v-for="color in colors"
|
||||
:key="color"
|
||||
:style="{ backgroundColor: color }"
|
||||
class="!size-5 @[280px]:!size-6"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
class="@max-[280px]:hidden typo-p-xs @[280px]:typo-p-md !font-semibold whitespace-nowrap"
|
||||
>
|
||||
{{ price }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1 px-2 items-start w-full text-black mt-4 @[280px]:hidden">
|
||||
<span class="typo-sub-h-sm w-full truncate">
|
||||
{{ title }}
|
||||
</span>
|
||||
<div class="@[280px]:hidden flex items-center justify-between w-full mt-1">
|
||||
<span class="typo-p-xs !font-semibold whitespace-nowrap">
|
||||
{{ price }}
|
||||
<div
|
||||
class="flex flex-col gap-1 px-2 items-start w-full text-black mt-4 @[280px]:hidden"
|
||||
>
|
||||
<span class="typo-sub-h-sm w-full truncate">
|
||||
{{ title }}
|
||||
</span>
|
||||
<div
|
||||
class="@[280px]:hidden flex items-center justify-between w-full mt-1"
|
||||
>
|
||||
<span
|
||||
class="typo-p-xs !font-semibold whitespace-nowrap"
|
||||
>
|
||||
{{ price }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
@@ -1,49 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
// imports
|
||||
|
||||
import useDeleteCartAll from "~/composables/api/orders/useDeleteCartAll";
|
||||
import { useAuth } from "~/composables/api/auth/useAuth";
|
||||
import useSignOut from "~/composables/api/auth/useSignOut";
|
||||
import { useToast } from "~/composables/global/useToast";
|
||||
import { QUERY_KEYS } from "~/constants";
|
||||
|
||||
// state
|
||||
|
||||
const { $queryClient: queryClient } = useNuxtApp();
|
||||
const router = useRouter();
|
||||
const { refreshToken, logout } = useAuth();
|
||||
const { addToast } = useToast();
|
||||
|
||||
const isShow = ref(false);
|
||||
|
||||
// queries
|
||||
|
||||
const { mutateAsync: deleteCartAll, isPending: deleteCartAllIsPending } =
|
||||
useDeleteCartAll();
|
||||
const { mutateAsync: signout, isPending: signoutIsPending } = useSignOut();
|
||||
|
||||
// methods
|
||||
|
||||
const handleSubmit = () => {
|
||||
deleteCartAll(undefined, {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.cart] });
|
||||
isShow.value = false;
|
||||
addToast({
|
||||
message: "سبد با موفقیت حذف شد",
|
||||
options: {
|
||||
status: "success",
|
||||
},
|
||||
});
|
||||
setTimeout(() => {
|
||||
router.push({ name: "index" });
|
||||
}, 1000);
|
||||
},
|
||||
onError: () => {
|
||||
addToast({
|
||||
message: "خطایی در حذف سبد رخ داد",
|
||||
options: {
|
||||
status: "error",
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
signout(
|
||||
{ refresh_token: refreshToken.value! },
|
||||
{
|
||||
onSuccess: () => {
|
||||
addToast({
|
||||
message: "با موفقیت از حساب خارج شدید",
|
||||
});
|
||||
logout(true);
|
||||
},
|
||||
onError: () => {
|
||||
addToast({
|
||||
message: "خطایی در خروج از حساب رخ داد",
|
||||
});
|
||||
isShow.value = false;
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -78,10 +70,10 @@ const handleSubmit = () => {
|
||||
size="md"
|
||||
>
|
||||
<Icon
|
||||
v-if="deleteCartAllIsPending"
|
||||
v-if="signoutIsPending"
|
||||
name="svg-spinners:3-dots-bounce"
|
||||
/>
|
||||
<span v-else>آره; دارم میرم</span>
|
||||
<span v-else>آره دارم میرم</span>
|
||||
</Button>
|
||||
<DialogClose aria-label="Close">
|
||||
<Button
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// imports
|
||||
|
||||
import { useMutation } from "@tanstack/vue-query";
|
||||
import { API_ENDPOINTS } from "~/constants";
|
||||
|
||||
// types
|
||||
|
||||
export type SignOutRequest = {
|
||||
refresh_token: string;
|
||||
};
|
||||
|
||||
const useSignOut = () => {
|
||||
// state
|
||||
|
||||
const { $axios: axios } = useNuxtApp();
|
||||
|
||||
// methods
|
||||
|
||||
const handleSignOut = async (params: SignOutRequest) => {
|
||||
const { data } = await axios.post(
|
||||
`${API_ENDPOINTS.auth.signout}`,
|
||||
params
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (params: SignOutRequest) => handleSignOut(params),
|
||||
});
|
||||
};
|
||||
|
||||
export default useSignOut;
|
||||
@@ -28,7 +28,8 @@ const useAddCartItem = () => {
|
||||
};
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (itemData: AddCartItemRequest) => handleAddCartItem(itemData),
|
||||
mutationFn: (itemData: AddCartItemRequest) =>
|
||||
handleAddCartItem(itemData),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export const API_ENDPOINTS = {
|
||||
refresh: "/token/refresh",
|
||||
verify: "/accounts/verify",
|
||||
signin: "/token",
|
||||
logout: "/accounts/logout",
|
||||
signout: "/accounts/logout",
|
||||
},
|
||||
chat: {
|
||||
messages: "/chat/product",
|
||||
|
||||
@@ -12,7 +12,9 @@ const prevPage = computed(() => route.meta.prevPage);
|
||||
|
||||
// queries
|
||||
|
||||
const { data: cart } = useGetCartOrders();
|
||||
const { data: cart, isPending: cartIsPending, suspense } = useGetCartOrders();
|
||||
|
||||
await suspense();
|
||||
|
||||
// computed
|
||||
|
||||
@@ -27,12 +29,13 @@ const hasCartItem = computed(
|
||||
dir="rtl"
|
||||
>
|
||||
<Header />
|
||||
|
||||
<main
|
||||
class="w-full overflow-x-hidden container flex flex-col gap-[5rem] max-w-[80vw]"
|
||||
class="w-full overflow-x-hidden flex flex-col gap-[5rem] lg:max-w-[85vw]"
|
||||
>
|
||||
<div class="w-full flex flex-col">
|
||||
<div class="w-full flex flex-col container">
|
||||
<div
|
||||
class="flex flex-col items-center justify-center gap-4 py-[5rem] lg:gap-0 lg:flex-row"
|
||||
class="flex flex-col items-center justify-center py-[3.5rem] lg:py-[5rem] gap-5 lg:gap-0 lg:flex-row"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-start w-full lg:w-3/12"
|
||||
@@ -53,7 +56,7 @@ const hasCartItem = computed(
|
||||
</div>
|
||||
|
||||
<h1
|
||||
class="w-full text-center typo-h-3 lg:w-6/12 title-large"
|
||||
class="w-full text-center lg:w-6/12 typo-h-5 lg:typo-h-4"
|
||||
>
|
||||
{{ pageTitle }}
|
||||
</h1>
|
||||
@@ -61,7 +64,7 @@ const hasCartItem = computed(
|
||||
<div class="hidden w-3/12 shrink-0 lg:block"> </div>
|
||||
</div>
|
||||
<div
|
||||
class="w-full flex flex-col items-center relative justify-between gap-8 lg:gap-6 lg:flex-row lg:items-start"
|
||||
class="w-full flex flex-col items-center relative justify-between gap-4 lg:gap-6 lg:flex-row lg:items-start"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col w-full gap-4 lg:gap-6 shrink-0"
|
||||
@@ -69,11 +72,10 @@ const hasCartItem = computed(
|
||||
>
|
||||
<NuxtPage />
|
||||
</div>
|
||||
|
||||
<CartSummary v-if="hasCartItem" />
|
||||
<CartSummary v-if="hasCartItem && !cartIsPending" />
|
||||
</div>
|
||||
</div>
|
||||
<ProductsSlider title="دیگران این محصولات را هم خریدهاند" />
|
||||
<ProductsSlider title="دیگر محصولات" />
|
||||
</main>
|
||||
<div class="w-full flex-col flex mt-20">
|
||||
<ServiceHighlights />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<main class="w-full h-[100svh] font-iran-yekan-x">
|
||||
<main class="w-full h-[100svh] font-iran-yekan-x overflow-x-hidden">
|
||||
<NuxtPage />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
@@ -84,28 +84,7 @@ const selectedGateway = ref<PaymentGateway>(paymentGateways.value[0]);
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col items-center w-full gap-4 p-4 border lg:gap-6 border-gray-300 rounded-xl bg-gray-50"
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-start w-full lg:text-[1.125rem] font-semibold text-gray-900"
|
||||
>
|
||||
خلاصه سفارش
|
||||
</span>
|
||||
|
||||
<div
|
||||
class="grid w-full grid-cols-1 gap-6 lg:grid-cols-3 md:grid-cols-2"
|
||||
>
|
||||
<MinimalCartItem v-for="i in 9" />
|
||||
|
||||
<div class="h-7 flex-center col-span-full lg:hidden">
|
||||
<button class="gap-2 flex-center">
|
||||
<span class="text-sm text-black"> مشاهده بیشتر </span>
|
||||
<Icon name="bi:chevron-down" class="**:stroke-black" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<OrderSummary />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -142,52 +142,9 @@ const handleSelectAddress = (address: Address) => {
|
||||
۱۵۰٬۰۰۰ تومان
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="flex items-center opacity-50 select-none pointer-events-none justify-between w-full p-3 transition-all border cursor-pointer delivery-option focus-within:ring-2 ring-black ring-offset-2 focus-within:border-black rounded-100 border-slate-200 bg-slate-50"
|
||||
>
|
||||
<div class="flex items-center gap-2.5">
|
||||
<SwitchRoot
|
||||
v-model="deliveryData.deliveryMethod.peyk"
|
||||
class="w-[3rem] h-[1.8rem] shrink-0 flex data-[state=unchecked]:bg-slate-200 data-[state=checked]:bg-black border border-slate-200 data-[state=checked]:border-black/20 rounded-full relative transition-all focus-within:outline-none"
|
||||
>
|
||||
<SwitchThumb
|
||||
class="size-6 my-auto bg-white text-sm ms-1 flex items-center justify-center shadow-xl rounded-full transition-transform translate-x-0.5 will-change-transform data-[state=checked]:-translate-x-[68%]"
|
||||
/>
|
||||
</SwitchRoot>
|
||||
<span class="w-full text-slate-800 text-sm lg:text-[1rem]"
|
||||
>ارسال با پیک (فقط ارسال درون شهری شیراز)</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<span class="text-slate-800 text-sm lg:text-[1rem]">
|
||||
۱۵۰٬۰۰۰ تومان
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col items-center w-full gap-4 p-4 border lg:gap-6 border-slate-200 rounded-xl bg-slate-50"
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-start w-full lg:text-[1.125rem] font-semibold text-slate-900"
|
||||
>
|
||||
خلاصه سفارش
|
||||
</span>
|
||||
|
||||
<div
|
||||
class="grid w-full grid-cols-1 gap-6 lg:grid-cols-3 md:grid-cols-2"
|
||||
>
|
||||
<MinimalCartItem v-for="i in 9" />
|
||||
|
||||
<div class="h-7 flex-center col-span-full lg:hidden">
|
||||
<button class="gap-2 flex-center">
|
||||
<span class="text-sm text-black"> مشاهده بیشتر </span>
|
||||
<Icon name="bi:chevron-down" class="**:stroke-black" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<OrderSummary />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ const hasCartItem = computed(
|
||||
<div class="w-full flex flex-col gap-4 lg:gap-6">
|
||||
<div
|
||||
v-if="hasCartItem"
|
||||
class="flex items-center justify-between w-full gap-3 px-5 py-4 rounded-xl bg-slate-50 border border-slate-200"
|
||||
class="flex items-center justify-between w-full gap-3 px-4 py-4 rounded-xl bg-slate-50 border border-slate-200"
|
||||
>
|
||||
<Skeleton
|
||||
v-if="cartIsLoading"
|
||||
|
||||
+20
-22
@@ -105,34 +105,32 @@ watch(
|
||||
</div>
|
||||
<ul
|
||||
v-if="productsIsLoading"
|
||||
class="w-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-[1.5rem]"
|
||||
class="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-y-8 gap-5 sm:gap-8 w-full"
|
||||
>
|
||||
<Skeleton
|
||||
v-for="i in 9"
|
||||
:key="i"
|
||||
class="w-full !h-[25rem] lg:!h-[22.5rem] !rounded-2xl"
|
||||
/>
|
||||
<div class="w-full flex flex-col gap-3" v-for="i in 8" :key="i">
|
||||
<Skeleton
|
||||
v-for="i in 3"
|
||||
:key="i"
|
||||
class="w-full"
|
||||
:class="{
|
||||
'!h-[11.75rem] lg:!h-[22.5rem] !rounded-2xl': i == 1,
|
||||
'!h-[1.35rem] lg:!h-[1.5rem] !rounded-sm lg:!hidden': [
|
||||
2, 3,
|
||||
].includes(i),
|
||||
'!w-1/2': i == 2,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</ul>
|
||||
<div v-else class="w-full h-max">
|
||||
<div v-if="!products?.length" class="flex flex-grow w-full">
|
||||
<Placeholder title="محصولی یافت نشد :(" icon="bi:search" />
|
||||
</div>
|
||||
<ul
|
||||
v-else
|
||||
class="w-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-[1.5rem]"
|
||||
>
|
||||
<li v-for="(product, index) in products" :key="index">
|
||||
<ProductCard
|
||||
:id="product.id"
|
||||
:title="product.name"
|
||||
:picture="product.variants[0].images[0].image"
|
||||
:colors="product.colors"
|
||||
:price="product.variants[0].price"
|
||||
:rate="product.rating"
|
||||
:dark-layer="true"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
<ProductsGrid
|
||||
:with-header="false"
|
||||
:products="products"
|
||||
class="!p-0"
|
||||
/>
|
||||
<div v-if="data?.count > 10" class="w-full flex-center py-10">
|
||||
<Pagination :items="paginationData" :total="data?.count" />
|
||||
</div>
|
||||
|
||||
@@ -150,7 +150,7 @@ const resetForm = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-svh items-center relative">
|
||||
<div class="w-full flex h-svh items-center relative container">
|
||||
<div
|
||||
class="bg-[url(/img/pattern-1.png)] -z-10 size-full absolute opacity-70"
|
||||
:style="{
|
||||
|
||||
Reference in New Issue
Block a user