merge
This commit is contained in:
@@ -36,7 +36,7 @@ const { isLoading: cartImageIsLoading } = useImage({
|
||||
|
||||
const { mutateAsync: deleteCartItem, isPending: deleteCartItemIsPending } = useDeleteCartItem();
|
||||
|
||||
const { mutateAsync: addCartItem } = useAddCartItem();
|
||||
const { mutateAsync: addCartItem, isPending: addItemInCartQuantityIsPending } = useAddCartItem();
|
||||
|
||||
// methods
|
||||
|
||||
@@ -153,7 +153,7 @@ watch(
|
||||
</div>
|
||||
|
||||
<NuxtLink
|
||||
:to="`product/${data.product.id}`"
|
||||
:to="{ name: 'product-id', params: { id: data.product.slug } }"
|
||||
class="font-semibold typo-sub-h-sm lg:typo-sub-h-xl text-black underline underline-offset-2"
|
||||
>
|
||||
{{ data.product.title }}
|
||||
@@ -196,7 +196,22 @@ watch(
|
||||
/>
|
||||
</button>
|
||||
|
||||
<div class="size-10 flex-center">{{ counter }}</div>
|
||||
<div
|
||||
v-if="addItemInCartQuantityIsPending"
|
||||
class="size-10 flex-center"
|
||||
>
|
||||
<Icon
|
||||
:name="'svg-spinners:180-ring-with-bg'"
|
||||
class="size-[18px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="size-10 flex-center"
|
||||
>
|
||||
{{ counter }}
|
||||
</div>
|
||||
|
||||
<button
|
||||
@click="handleDecreaseQuantity"
|
||||
@@ -246,7 +261,20 @@ watch(
|
||||
/>
|
||||
</button>
|
||||
|
||||
<div class="size-10 text-sm flex-center">
|
||||
<div
|
||||
v-if="addItemInCartQuantityIsPending"
|
||||
class="size-10 text-sm flex-center"
|
||||
>
|
||||
<Icon
|
||||
:name="'svg-spinners:180-ring-with-bg'"
|
||||
class="size-[25px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="size-10 text-sm flex-center"
|
||||
>
|
||||
{{ counter }}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
// types
|
||||
import { useImage } from "@vueuse/core";
|
||||
import { useRatio } from "~/composables/global/useRatio";
|
||||
|
||||
// types
|
||||
type Props = {
|
||||
// brands: string[];
|
||||
// brands?: string[];
|
||||
};
|
||||
|
||||
// props
|
||||
|
||||
const { isMobile } = useRatio();
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const {} = toRefs(props);
|
||||
|
||||
@@ -18,6 +22,15 @@ const brands = ref([
|
||||
"/img/brands/brand-5.png",
|
||||
"/img/brands/brand-6.png",
|
||||
]);
|
||||
|
||||
// preload images using vueuse
|
||||
const isReady = ref(false);
|
||||
|
||||
onMounted(async () => {
|
||||
const loaders = brands.value.map((src) => useImage({ src }));
|
||||
await Promise.all(loaders.map((l) => (l.isLoading.value ? l.promise : Promise.resolve())));
|
||||
isReady.value = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -29,48 +42,68 @@ const brands = ref([
|
||||
متون بلکه روزنامه و مجله در ستون و سطرآنچنان که
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- TOP MARQUEE -->
|
||||
<div class="-rotate-z-2 z-20 w-[110%] shadow-2xl shadow-black/7">
|
||||
<Marquee
|
||||
class="bg-blue-500 h-full"
|
||||
class="bg-blue-500 will-change-transform"
|
||||
:clone="true"
|
||||
dir="ltr"
|
||||
:duration="3"
|
||||
:duration="14"
|
||||
>
|
||||
<div class="flex items-center gap-12 sm:gap-20 px-6 sm:px-10 h-[90px] sm:h-[140px]">
|
||||
<div
|
||||
v-for="i in 6"
|
||||
:key="i"
|
||||
class="flex items-center gap-12 sm:gap-20 px-6 sm:px-10 h-[70px] sm:h-[140px] shrink-0 grow-0"
|
||||
>
|
||||
<div class="text-[30px] text-white lg:text-[40px] mt-2 whitespace-nowrap font-semibold opacity-85">
|
||||
HEYMLZ
|
||||
</div>
|
||||
<NuxtImg
|
||||
loading="lazy"
|
||||
fetch-priority="low"
|
||||
src="/img/heymlz/heymlz-logo.png"
|
||||
class="h-[25px] sm:h-[45px] invert"
|
||||
/>
|
||||
<div class="size-[25px] sm:size-[45px] flex-center">
|
||||
<NuxtImg
|
||||
loading="lazy"
|
||||
fetch-priority="low"
|
||||
src="/img/heymlz/heymlz-logo.png"
|
||||
class="w-full object-contain invert"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Marquee>
|
||||
</div>
|
||||
|
||||
<!-- BOTTOM MARQUEE -->
|
||||
<div class="rotate-z-2 z-10 w-[110%]">
|
||||
<Marquee
|
||||
class="bg-slate-100/70"
|
||||
:direction="'reverse'"
|
||||
v-if="isReady"
|
||||
class="bg-slate-100/70 will-change-transform"
|
||||
direction="reverse"
|
||||
:clone="true"
|
||||
dir="ltr"
|
||||
:duration="10"
|
||||
:duration="14"
|
||||
>
|
||||
<div
|
||||
v-for="brand in brands"
|
||||
:key="brand"
|
||||
class="flex items-center px-6 sm:px-10 h-[90px] sm:h-[140px]"
|
||||
class="flex items-center px-6 sm:px-10 h-[70px] sm:h-[140px] shrink-0 grow-0"
|
||||
>
|
||||
<NuxtImg
|
||||
loading="lazy"
|
||||
fetch-priority="low"
|
||||
:src="brand"
|
||||
class="h-[25px] sm:h-[45px] opacity-25"
|
||||
class="opacity-25 object-contain w-[120px] sm:w-[200px] h-[45px] sm:h-[90px]"
|
||||
/>
|
||||
</div>
|
||||
</Marquee>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.4s ease;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,7 +5,7 @@ type Props = {
|
||||
error?: boolean;
|
||||
message?: string;
|
||||
placeholder?: string;
|
||||
modelValue: string;
|
||||
modelValue: string | undefined;
|
||||
};
|
||||
|
||||
// props
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
// imports
|
||||
|
||||
import { useAppParams } from "~/composables/global/useAppParams";
|
||||
import { useRatio } from "~/composables/global/useRatio";
|
||||
|
||||
// types
|
||||
@@ -20,27 +21,9 @@ defineProps<Props>();
|
||||
|
||||
// state
|
||||
|
||||
const params: any = inject("params");
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const { page } = useAppParams();
|
||||
|
||||
const { isMobile } = useRatio();
|
||||
|
||||
const { y } = useWindowScroll({ behavior: "smooth" });
|
||||
|
||||
// computed
|
||||
|
||||
const page = computed({
|
||||
get: () => (route.query["page"] ? Number(route.query["page"]) : 1),
|
||||
set: (value: number) => {
|
||||
params.page = value;
|
||||
router.push({
|
||||
query: { ...route.query, page: value },
|
||||
});
|
||||
y.value = 0;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -22,8 +22,10 @@ const emit = defineEmits<Emits>();
|
||||
// computed
|
||||
|
||||
const value = computed({
|
||||
get: () => modelValue.value ?? false,
|
||||
set: (value) => emit("update:modelValue", value),
|
||||
get: () => (modelValue.value == "true" ? true : false),
|
||||
set: (value) => {
|
||||
emit("update:modelValue", value);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ watch(
|
||||
size="md"
|
||||
class="rounded-full max-xs:typo-label-sm flex items-center justify-center gap-1.5"
|
||||
>
|
||||
{{ isDescriptionCollapsed ? 'نمایش بیشتر' : 'نمایش کمتر' }}
|
||||
{{ isDescriptionCollapsed ? "نمایش بیشتر" : "نمایش کمتر" }}
|
||||
<Icon
|
||||
name="ci:chevron-left"
|
||||
size="16"
|
||||
|
||||
@@ -2,62 +2,44 @@
|
||||
// imports
|
||||
|
||||
import useGetCategories from "~/composables/api/categories/useGetCategories";
|
||||
import useGetProducts, { type GetProductsFilters } from "~/composables/api/products/useGetProducts";
|
||||
import { PRODUCT_RANGE } from "~/constants";
|
||||
import useGetProducts from "~/composables/api/products/useGetProducts";
|
||||
import { useAppParams } from "~/composables/global/useAppParams";
|
||||
import { PRODUCT_RANGE, PRODUCTS_SORTS } from "~/constants";
|
||||
|
||||
// state
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const params = inject("params") as GetProductsFilters;
|
||||
const { sort, in_stock, has_discount, price_gte, price_lte, search, page, slug } = useAppParams();
|
||||
|
||||
const isSideShow = useState("side-modal-product-filters");
|
||||
|
||||
const currentCategory = computed({
|
||||
get: () => {
|
||||
return Array.isArray(route.params.slug) ? route.params.slug[1] ?? undefined : undefined;
|
||||
return Array.isArray(slug.value) ? slug.value[1] ?? undefined : undefined;
|
||||
},
|
||||
set: (newValue) => {
|
||||
router.push({ path: `/products/category/${newValue}`, query: { ...route.query } });
|
||||
isSideShow.value = false;
|
||||
router.push({
|
||||
name: "products-slug",
|
||||
params: { slug: ["category", newValue] },
|
||||
query: { ...route.query },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const sort_filter = ref([
|
||||
{ title: "جدیدترین ها", value: "newest" },
|
||||
{ title: "گران ترین ها", value: "price" },
|
||||
{ title: "ارزان ترین ها", value: "-price" },
|
||||
]);
|
||||
|
||||
const sliderValue = ref([params.price_gte ?? PRODUCT_RANGE.min, params.price_lte ?? PRODUCT_RANGE.max]);
|
||||
|
||||
const has_discount = ref(Boolean(params.has_discount) ?? false);
|
||||
const in_stock = ref(Boolean(params.in_stock) ?? false);
|
||||
const sliderValue = ref([price_gte.value ?? PRODUCT_RANGE.min, price_lte.value ?? PRODUCT_RANGE.max]);
|
||||
|
||||
const sliderValueDebounced = refDebounced(sliderValue, 1000);
|
||||
|
||||
const filtersSuccessMessage = ref<{ title: string; status: string } | null>(null);
|
||||
|
||||
// queries
|
||||
|
||||
const filters = computed(() => {
|
||||
return {
|
||||
sort: params.sort ?? "newest",
|
||||
search: params.search ?? "",
|
||||
price_gte: params.price_gte ?? PRODUCT_RANGE.min,
|
||||
price_lte: params.price_lte ?? PRODUCT_RANGE.max,
|
||||
in_stock: params.in_stock ?? false,
|
||||
has_discount: params.has_discount ?? false,
|
||||
category: currentCategory.value,
|
||||
page: params.page ?? 1,
|
||||
};
|
||||
});
|
||||
|
||||
const { data: categories, suspense } = useGetCategories();
|
||||
|
||||
await suspense();
|
||||
|
||||
const { isPending: productsIsPending, status: productsStatus } = useGetProducts(filters);
|
||||
const { isPending: productsIsPending } = useGetProducts();
|
||||
|
||||
// computed
|
||||
|
||||
@@ -80,49 +62,23 @@ const allCategories = computed(() => {
|
||||
// methods
|
||||
|
||||
const resetFilters = () => {
|
||||
params.search = "";
|
||||
params.sort = "";
|
||||
search.value = "";
|
||||
sort.value = "";
|
||||
sliderValue.value = [PRODUCT_RANGE.min, PRODUCT_RANGE.max];
|
||||
has_discount.value = false;
|
||||
in_stock.value = false;
|
||||
params.page = 1;
|
||||
has_discount.value = "false";
|
||||
in_stock.value = "false";
|
||||
page.value = 1;
|
||||
|
||||
router.push({ path: `/products/`, query: { ...route.query, page: 1 } });
|
||||
isSideShow.value = false;
|
||||
};
|
||||
|
||||
// watch
|
||||
|
||||
watch(
|
||||
() => sliderValueDebounced.value,
|
||||
(newValue) => {
|
||||
params.price_gte = newValue[0];
|
||||
params.price_lte = newValue[1];
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [has_discount.value, in_stock.value],
|
||||
([newHasDiscount, newInStock]) => {
|
||||
params.has_discount = newHasDiscount;
|
||||
params.in_stock = newInStock;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => productsStatus.value,
|
||||
(nv) => {
|
||||
if (nv == "success") {
|
||||
filtersSuccessMessage.value = {
|
||||
title: "فیلتر اعمال شد",
|
||||
status: nv,
|
||||
};
|
||||
} else if (nv == "error") {
|
||||
filtersSuccessMessage.value = {
|
||||
title: "خطایی در اعمال فیلتر رخ داد",
|
||||
status: nv,
|
||||
};
|
||||
}
|
||||
setTimeout(() => {
|
||||
filtersSuccessMessage.value = null;
|
||||
}, 4000);
|
||||
price_gte.value = newValue[0];
|
||||
price_lte.value = newValue[1];
|
||||
}
|
||||
);
|
||||
</script>
|
||||
@@ -140,13 +96,13 @@ watch(
|
||||
</div>
|
||||
<div class="w-full flex items-center gap-2">
|
||||
<button
|
||||
v-for="(sort, index) in sort_filter"
|
||||
v-for="(sort_filter, index) in PRODUCTS_SORTS"
|
||||
:key="index"
|
||||
@click="params.sort = sort.value"
|
||||
:class="params.sort == sort.value ? 'bg-black text-white' : 'bg-slate-100'"
|
||||
@click="sort = sort_filter.value"
|
||||
:class="sort == sort_filter.value ? 'bg-black text-white' : 'bg-slate-100'"
|
||||
class="py-1 px-3 cursor-pointer text-nowrap transition-all rounded-md text-xs lg:text-sm"
|
||||
>
|
||||
{{ sort.title }}
|
||||
{{ sort_filter.title }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -219,80 +175,58 @@ watch(
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex flex-col items-center gap-5">
|
||||
<Transition
|
||||
enter-active-class="animate__animated animate__fadeInUp animate__faster"
|
||||
leave-active-class="animate__animated animate__fadeOutDown animate__faster"
|
||||
<div class="flex items-center gap-4 w-full">
|
||||
<Button
|
||||
:disabled="productsIsPending"
|
||||
variant="primary"
|
||||
@click="isSideShow = false"
|
||||
class="w-full rounded-xl py-3 !cursor-pointer disabled:pointer-events-none z-[3]"
|
||||
>
|
||||
<div
|
||||
v-if="!!filtersSuccessMessage"
|
||||
class="w-max rounded-full py-1.5 px-3 text-xs border flex-center gap-0.5 z-[2]"
|
||||
:class="
|
||||
filtersSuccessMessage.status == 'success'
|
||||
? 'text-success-600 bg-success-100 border-success-600'
|
||||
: ' text-danger-600 bg-danger-100 border-danger-600'
|
||||
"
|
||||
<Transition
|
||||
name="fade"
|
||||
mode="out-in"
|
||||
>
|
||||
<span class="text-sm">{{ filtersSuccessMessage.title }}</span>
|
||||
<Icon
|
||||
:name="filtersSuccessMessage.status == 'success' ? 'bi:check' : 'bi:x'"
|
||||
size="20"
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
<div class="flex items-center gap-4 w-full">
|
||||
<Button
|
||||
:disabled="productsIsPending"
|
||||
variant="primary"
|
||||
@click="() => (isSideShow = false)"
|
||||
class="w-full rounded-full py-4 !cursor-pointer disabled:pointer-events-none z-[3]"
|
||||
<span class="flex-center gap-3 text-sm">
|
||||
اعمال فیلتر
|
||||
<Icon
|
||||
name="ci:plus"
|
||||
size="20"
|
||||
/>
|
||||
</span>
|
||||
</Transition>
|
||||
</Button>
|
||||
<Button
|
||||
:disabled="productsIsPending"
|
||||
variant="solid"
|
||||
@click="resetFilters"
|
||||
class="w-full rounded-xl py-3 !cursor-pointer disabled:pointer-events-none z-[3]"
|
||||
>
|
||||
<Transition
|
||||
name="fade"
|
||||
mode="out-in"
|
||||
>
|
||||
<Transition
|
||||
name="fade"
|
||||
mode="out-in"
|
||||
<span
|
||||
v-if="productsIsPending"
|
||||
class="flex-center gap-3 text-sm"
|
||||
>
|
||||
<span class="flex-center gap-3">
|
||||
ثبت فیلتر
|
||||
<Icon
|
||||
name="ci:plus"
|
||||
size="20"
|
||||
/>
|
||||
</span>
|
||||
</Transition>
|
||||
</Button>
|
||||
<Button
|
||||
:disabled="productsIsPending"
|
||||
variant="solid"
|
||||
@click="resetFilters"
|
||||
class="w-full rounded-full py-4 !cursor-pointer disabled:pointer-events-none z-[3]"
|
||||
>
|
||||
<Transition
|
||||
name="fade"
|
||||
mode="out-in"
|
||||
در حال دریافت اطلاعات
|
||||
<Icon
|
||||
name="svg-spinners:3-dots-bounce"
|
||||
size="20"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="flex-center gap-3 text-sm"
|
||||
>
|
||||
<span
|
||||
v-if="productsIsPending"
|
||||
class="flex-center gap-3"
|
||||
>
|
||||
در حال دریافت اطلاعات
|
||||
<Icon
|
||||
name="svg-spinners:3-dots-bounce"
|
||||
size="20"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="flex-center gap-3"
|
||||
>
|
||||
بازنشانی به پیش فرض
|
||||
<Icon
|
||||
name="ci:close"
|
||||
size="20"
|
||||
/>
|
||||
</span>
|
||||
</Transition>
|
||||
</Button>
|
||||
</div>
|
||||
بازنشانی به پیش فرض
|
||||
<Icon
|
||||
name="ci:close"
|
||||
size="20"
|
||||
/>
|
||||
</span>
|
||||
</Transition>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2,62 +2,44 @@
|
||||
// imports
|
||||
|
||||
import useGetResellersCategories from "~/composables/api/resellers/useGetResellersCategories";
|
||||
import useGetResellersProducts, {
|
||||
type GetResellersProductsFilters,
|
||||
} from "~/composables/api/resellers/useGetResellersProducts";
|
||||
import { PRODUCT_RANGE } from "~/constants";
|
||||
import useGetResellersProducts from "~/composables/api/resellers/useGetResellersProducts";
|
||||
import { useAppParams } from "~/composables/global/useAppParams";
|
||||
import { PRODUCT_RANGE, PRODUCTS_SORTS } from "~/constants";
|
||||
|
||||
// state
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const params = inject("params") as GetResellersProductsFilters;
|
||||
const { sort, in_stock, has_discount, price_gte, price_lte, search, page, slug } = useAppParams();
|
||||
|
||||
const isSideShow = useState("side-modal-resellers-product-filters");
|
||||
|
||||
const currentCategory = computed({
|
||||
get: () => {
|
||||
return Array.isArray(route.params.slug) ? route.params.slug[1] ?? undefined : undefined;
|
||||
return Array.isArray(slug.value) ? slug.value[1] ?? undefined : undefined;
|
||||
},
|
||||
set: (newValue) => {
|
||||
router.push({ path: `/resellers/category/${newValue}`, query: { ...route.query } });
|
||||
isSideShow.value = false;
|
||||
router.push({
|
||||
name: "resellers-slug",
|
||||
params: { slug: ["category", `${newValue}`] },
|
||||
query: { ...route.query },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const sort_filter = ref([
|
||||
{ title: "جدیدترین ها", value: "newest" },
|
||||
{ title: "گران ترین ها", value: "price" },
|
||||
{ title: "ارزان ترین ها", value: "-price" },
|
||||
]);
|
||||
|
||||
const sliderValue = ref([params.price_gte ?? PRODUCT_RANGE.min, params.price_lte ?? PRODUCT_RANGE.max]);
|
||||
|
||||
const has_discount = ref(Boolean(params.has_discount) ?? false);
|
||||
const in_stock = ref(Boolean(params.in_stock) ?? false);
|
||||
const sliderValue = ref([price_gte.value ?? PRODUCT_RANGE.min, price_lte.value ?? PRODUCT_RANGE.max]);
|
||||
|
||||
const sliderValueDebounced = refDebounced(sliderValue, 1000);
|
||||
|
||||
const filtersSuccessMessage = ref<{ title: string; status: string } | null>(null);
|
||||
|
||||
// queries
|
||||
|
||||
const filters = computed(() => {
|
||||
return {
|
||||
sort: params.sort ?? "newest",
|
||||
search: params.search ?? "",
|
||||
price_gte: params.price_gte ?? PRODUCT_RANGE.min,
|
||||
price_lte: params.price_lte ?? PRODUCT_RANGE.max,
|
||||
in_stock: params.in_stock ?? false,
|
||||
has_discount: params.has_discount ?? false,
|
||||
category: currentCategory.value,
|
||||
page: params.page ?? 1,
|
||||
};
|
||||
});
|
||||
|
||||
const { data: categories, suspense } = useGetResellersCategories();
|
||||
|
||||
await suspense();
|
||||
|
||||
const { isPending: productsIsPending, status: productsStatus } = useGetResellersProducts(filters);
|
||||
const { isPending: productsIsPending } = useGetResellersProducts();
|
||||
|
||||
// computed
|
||||
|
||||
@@ -70,48 +52,23 @@ const allCategories = computed(() => {
|
||||
// methods
|
||||
|
||||
const resetFilters = () => {
|
||||
params.search = "";
|
||||
params.sort = "";
|
||||
search.value = "";
|
||||
sort.value = "";
|
||||
sliderValue.value = [PRODUCT_RANGE.min, PRODUCT_RANGE.max];
|
||||
has_discount.value = false;
|
||||
in_stock.value = false;
|
||||
has_discount.value = "false";
|
||||
in_stock.value = "false";
|
||||
page.value = 1;
|
||||
|
||||
router.push({ path: `/resellers/`, query: { ...route.query } });
|
||||
isSideShow.value = false;
|
||||
};
|
||||
|
||||
// watch
|
||||
|
||||
watch(
|
||||
() => sliderValueDebounced.value,
|
||||
(newValue) => {
|
||||
params.price_gte = newValue[0];
|
||||
params.price_lte = newValue[1];
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [has_discount.value, in_stock.value],
|
||||
([newHasDiscount, newInStock]) => {
|
||||
params.has_discount = newHasDiscount;
|
||||
params.in_stock = newInStock;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => productsStatus.value,
|
||||
(nv) => {
|
||||
if (nv == "success") {
|
||||
filtersSuccessMessage.value = {
|
||||
title: "فیلتر اعمال شد",
|
||||
status: nv,
|
||||
};
|
||||
} else if (nv == "error") {
|
||||
filtersSuccessMessage.value = {
|
||||
title: "خطایی در اعمال فیلتر رخ داد",
|
||||
status: nv,
|
||||
};
|
||||
}
|
||||
setTimeout(() => {
|
||||
filtersSuccessMessage.value = null;
|
||||
}, 4000);
|
||||
price_gte.value = newValue[0];
|
||||
price_lte.value = newValue[1];
|
||||
}
|
||||
);
|
||||
</script>
|
||||
@@ -129,13 +86,13 @@ watch(
|
||||
</div>
|
||||
<div class="w-full flex items-center gap-2">
|
||||
<button
|
||||
v-for="(sort, index) in sort_filter"
|
||||
v-for="(sort_filter, index) in PRODUCTS_SORTS"
|
||||
:key="index"
|
||||
@click="params.sort = sort.value"
|
||||
:class="params.sort == sort.value ? 'bg-black text-white' : 'bg-slate-100'"
|
||||
@click="sort = sort_filter.value"
|
||||
:class="sort == sort_filter.value ? 'bg-black text-white' : 'bg-slate-100'"
|
||||
class="py-1 px-3 cursor-pointer text-nowrap transition-all rounded-md text-xs lg:text-sm"
|
||||
>
|
||||
{{ sort.title }}
|
||||
{{ sort_filter.title }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -209,32 +166,31 @@ watch(
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex flex-col items-center gap-5">
|
||||
<Transition
|
||||
enter-active-class="animate__animated animate__fadeInUp animate__faster"
|
||||
leave-active-class="animate__animated animate__fadeOutDown animate__faster"
|
||||
<div class="flex items-center gap-4 w-full">
|
||||
<Button
|
||||
:disabled="productsIsPending"
|
||||
variant="primary"
|
||||
@click="isSideShow = false"
|
||||
class="w-full rounded-xl py-3 !cursor-pointer disabled:pointer-events-none z-[3]"
|
||||
>
|
||||
<div
|
||||
v-if="!!filtersSuccessMessage"
|
||||
class="w-max rounded-full py-1.5 px-3 text-xs border flex-center gap-0.5 z-[2]"
|
||||
:class="
|
||||
filtersSuccessMessage.status == 'success'
|
||||
? 'text-success-600 bg-success-100 border-success-600'
|
||||
: ' text-danger-600 bg-danger-100 border-danger-600'
|
||||
"
|
||||
<Transition
|
||||
name="fade"
|
||||
mode="out-in"
|
||||
>
|
||||
<span class="text-sm">{{ filtersSuccessMessage.title }}</span>
|
||||
<Icon
|
||||
:name="filtersSuccessMessage.status == 'success' ? 'bi:check' : 'bi:x'"
|
||||
size="20"
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
<span class="flex-center gap-3 text-sm">
|
||||
اعمال فیلتر
|
||||
<Icon
|
||||
name="ci:plus"
|
||||
size="20"
|
||||
/>
|
||||
</span>
|
||||
</Transition>
|
||||
</Button>
|
||||
<Button
|
||||
:disabled="productsIsPending"
|
||||
variant="solid"
|
||||
@click="resetFilters"
|
||||
class="w-full rounded-full py-4 !cursor-pointer disabled:pointer-events-none z-[3]"
|
||||
class="w-full rounded-xl py-3 !cursor-pointer disabled:pointer-events-none z-[3]"
|
||||
>
|
||||
<Transition
|
||||
name="fade"
|
||||
@@ -242,7 +198,7 @@ watch(
|
||||
>
|
||||
<span
|
||||
v-if="productsIsPending"
|
||||
class="flex-center gap-3"
|
||||
class="flex-center gap-3 text-sm"
|
||||
>
|
||||
در حال دریافت اطلاعات
|
||||
<Icon
|
||||
@@ -252,7 +208,7 @@ watch(
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="flex-center gap-3"
|
||||
class="flex-center gap-3 text-sm"
|
||||
>
|
||||
بازنشانی به پیش فرض
|
||||
<Icon
|
||||
|
||||
@@ -1,31 +1,28 @@
|
||||
// imports
|
||||
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { useAppParams } from "~/composables/global/useAppParams";
|
||||
import { API_ENDPOINTS, QUERY_KEYS } from "~/constants";
|
||||
|
||||
// types
|
||||
|
||||
export type GetAllNotificationsResponse = ApiPaginated<Notification>;
|
||||
|
||||
export type GetAllNotificationsRequest = {
|
||||
sort: string | undefined;
|
||||
status: string | undefined;
|
||||
page: string | string[];
|
||||
};
|
||||
|
||||
const useGetAllNotifications = (params: ComputedRef<GetAllNotificationsRequest>) => {
|
||||
const useGetAllNotifications = () => {
|
||||
// state
|
||||
|
||||
const { $axios: axios } = useNuxtApp();
|
||||
|
||||
const { sort, status, page } = useAppParams();
|
||||
|
||||
// methods
|
||||
|
||||
const handleGetAllNotifications = async (params: GetAllNotificationsRequest) => {
|
||||
const handleGetAllNotifications = async () => {
|
||||
const { data } = await axios.get<GetAllNotificationsResponse>(API_ENDPOINTS.account.notifications.get_all, {
|
||||
params: {
|
||||
sort: params.sort,
|
||||
filter: params.status,
|
||||
offset: Number(params.page) * 10 - 10,
|
||||
sort: sort.value ?? "created_at",
|
||||
filter: status.value,
|
||||
offset: Number(page.value) * 10 - 10,
|
||||
limit: 10,
|
||||
},
|
||||
});
|
||||
@@ -33,8 +30,8 @@ const useGetAllNotifications = (params: ComputedRef<GetAllNotificationsRequest>)
|
||||
};
|
||||
|
||||
return useQuery({
|
||||
queryKey: [QUERY_KEYS.notifications, params],
|
||||
queryFn: () => handleGetAllNotifications(params.value),
|
||||
queryKey: [QUERY_KEYS.notifications, sort, status, page],
|
||||
queryFn: () => handleGetAllNotifications(),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// imports
|
||||
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { useAppParams } from "~/composables/global/useAppParams";
|
||||
import { API_ENDPOINTS, QUERY_KEYS } from "~/constants";
|
||||
|
||||
// types
|
||||
@@ -13,32 +14,30 @@ export type GetAllOrdersRequest = {
|
||||
page: string | string[];
|
||||
};
|
||||
|
||||
const useGetAllOrders = (params: ComputedRef<GetAllOrdersRequest>) => {
|
||||
|
||||
const useGetAllOrders = () => {
|
||||
// state
|
||||
|
||||
const { $axios: axios } = useNuxtApp();
|
||||
|
||||
const { sort, status, page } = useAppParams();
|
||||
|
||||
// methods
|
||||
|
||||
const handleGetAllOrders = async (params: GetAllOrdersRequest) => {
|
||||
const { data } = await axios.get<GetAllOrdersResponse>(
|
||||
API_ENDPOINTS.orders.get_all,
|
||||
{
|
||||
params: {
|
||||
sort: params.sort,
|
||||
filter: params.status,
|
||||
offset: Number(params.page) * 7 - 7,
|
||||
limit: 7,
|
||||
},
|
||||
}
|
||||
);
|
||||
const handleGetAllOrders = async () => {
|
||||
const { data } = await axios.get<GetAllOrdersResponse>(API_ENDPOINTS.orders.get_all, {
|
||||
params: {
|
||||
sort: sort.value ?? "created_at",
|
||||
filter: status.value,
|
||||
offset: Number(page.value) * 10 - 10,
|
||||
limit: 10,
|
||||
},
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
return useQuery({
|
||||
queryKey: [QUERY_KEYS.orders, params],
|
||||
queryFn: () => handleGetAllOrders(params.value),
|
||||
queryKey: [QUERY_KEYS.orders, sort, status, page],
|
||||
queryFn: () => handleGetAllOrders(),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
// imports
|
||||
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { useAppParams } from "~/composables/global/useAppParams";
|
||||
import { API_ENDPOINTS, QUERY_KEYS } from "~/constants";
|
||||
|
||||
// types
|
||||
|
||||
export type GetTransactionResponse = Transaction;
|
||||
|
||||
export type GetTransactionRequest = string;
|
||||
|
||||
const useGetTransaction = (params: ComputedRef<GetTransactionRequest>) => {
|
||||
const useGetTransaction = () => {
|
||||
// state
|
||||
|
||||
const { $axios: axios } = useNuxtApp();
|
||||
|
||||
const { tracking_code } = useAppParams();
|
||||
|
||||
// methods
|
||||
|
||||
const handleGetTransaction = async (tc: GetTransactionRequest) => {
|
||||
const handleGetTransaction = async () => {
|
||||
const { data } = await axios.get<GetTransactionResponse>(
|
||||
`${API_ENDPOINTS.orders.checkout.transaction}/${tc}`
|
||||
`${API_ENDPOINTS.orders.checkout.transaction}/${tracking_code.value}`
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
return useQuery({
|
||||
queryKey: [QUERY_KEYS.transaction, params],
|
||||
queryFn: () => handleGetTransaction(params.value),
|
||||
queryKey: [QUERY_KEYS.transaction, tracking_code],
|
||||
queryFn: () => handleGetTransaction(),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,57 +1,105 @@
|
||||
// imports
|
||||
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { useAppParams } from "~/composables/global/useAppParams";
|
||||
import { API_ENDPOINTS, QUERY_KEYS } from "~/constants";
|
||||
|
||||
// types
|
||||
|
||||
export type GetProductsResponse = ApiPaginated<ProductListItem>;
|
||||
|
||||
export type GetProductsFilters = {
|
||||
search?: string | undefined;
|
||||
sort?: string | undefined;
|
||||
category?: string | undefined;
|
||||
price_gte: number;
|
||||
price_lte: number;
|
||||
has_discount?: boolean | undefined;
|
||||
in_stock?: boolean | undefined;
|
||||
page: number;
|
||||
};
|
||||
|
||||
// composable
|
||||
|
||||
const useGetProducts = (params?: ComputedRef<GetProductsFilters>) => {
|
||||
const useGetProducts = () => {
|
||||
// state
|
||||
|
||||
const { $axios: axios } = useNuxtApp();
|
||||
const { $axios: axios, $queryClient: queryClient } = useNuxtApp();
|
||||
|
||||
const { search, sort, in_stock, has_discount, slug, price_gte, price_lte, page } = useAppParams();
|
||||
|
||||
const searchDebounced = refDebounced(search, 500);
|
||||
|
||||
// computed
|
||||
|
||||
const products_category = computed(() => {
|
||||
if (Array.isArray(slug.value) && slug.value.length >= 2) {
|
||||
return slug.value[1];
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const filters_clone = computed(() => {
|
||||
return {
|
||||
search,
|
||||
sort,
|
||||
in_stock,
|
||||
has_discount,
|
||||
slug,
|
||||
price_gte,
|
||||
price_lte,
|
||||
};
|
||||
});
|
||||
|
||||
// watch
|
||||
|
||||
watch(
|
||||
() => filters_clone.value,
|
||||
() => {
|
||||
queryClient.cancelQueries({
|
||||
queryKey: [
|
||||
QUERY_KEYS.products,
|
||||
searchDebounced,
|
||||
sort,
|
||||
in_stock,
|
||||
has_discount,
|
||||
products_category,
|
||||
price_gte,
|
||||
price_lte,
|
||||
page,
|
||||
],
|
||||
});
|
||||
page.value = 1;
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
// methods
|
||||
|
||||
const handleGetProducts = async (params?: GetProductsFilters) => {
|
||||
const { data } = await axios.get<GetProductsResponse>(
|
||||
`${API_ENDPOINTS.products.get_all}`,
|
||||
{
|
||||
params: {
|
||||
sort: params?.sort,
|
||||
in_stock: params?.in_stock,
|
||||
search: params?.search,
|
||||
has_discount: params?.has_discount,
|
||||
category: params?.category,
|
||||
price_gte: params?.price_gte,
|
||||
price_lte: params?.price_lte,
|
||||
offset: Number(params?.page) * 15 - 15,
|
||||
limit: 15
|
||||
}
|
||||
}
|
||||
);
|
||||
const handleGetProducts = async ({ signal }: { signal: AbortSignal }) => {
|
||||
const { data } = await axios.get<GetProductsResponse>(`${API_ENDPOINTS.products.get_all}`, {
|
||||
params: {
|
||||
sort: sort.value || "newest",
|
||||
in_stock: in_stock.value,
|
||||
search: searchDebounced.value,
|
||||
has_discount: has_discount.value,
|
||||
category: products_category.value,
|
||||
price_gte: price_gte.value,
|
||||
price_lte: price_lte.value,
|
||||
offset: Number(page.value) * 15 - 15,
|
||||
limit: 15,
|
||||
},
|
||||
signal,
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
return useQuery({
|
||||
staleTime: 60 * 1000,
|
||||
queryKey: [QUERY_KEYS.products, params],
|
||||
queryFn: () => handleGetProducts(params?.value)
|
||||
queryKey: [
|
||||
QUERY_KEYS.products,
|
||||
searchDebounced,
|
||||
sort,
|
||||
in_stock,
|
||||
has_discount,
|
||||
products_category,
|
||||
price_gte,
|
||||
price_lte,
|
||||
page,
|
||||
],
|
||||
queryFn: ({ signal }) => handleGetProducts({ signal }),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,45 +1,86 @@
|
||||
// imports
|
||||
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { useAppParams } from "~/composables/global/useAppParams";
|
||||
import { API_ENDPOINTS, QUERY_KEYS } from "~/constants";
|
||||
|
||||
// types
|
||||
|
||||
export type GetResellersProductsResponse = ApiPaginated<ProductListItem>;
|
||||
|
||||
export type GetResellersProductsFilters = {
|
||||
search?: string | undefined;
|
||||
sort?: string | undefined;
|
||||
category?: string | undefined;
|
||||
price_gte: number;
|
||||
price_lte: number;
|
||||
has_discount?: boolean | undefined;
|
||||
in_stock?: boolean | undefined;
|
||||
page: number;
|
||||
};
|
||||
|
||||
// composable
|
||||
|
||||
const useGetResellersProducts = (params?: ComputedRef<GetResellersProductsFilters>) => {
|
||||
const useGetResellersProducts = () => {
|
||||
// state
|
||||
|
||||
const { $axios: axios } = useNuxtApp();
|
||||
const { $axios: axios, $queryClient: queryClient } = useNuxtApp();
|
||||
|
||||
const { search, sort, in_stock, has_discount, slug, price_gte, price_lte, page } = useAppParams();
|
||||
|
||||
const searchDebounced = refDebounced(search, 500);
|
||||
|
||||
// computed
|
||||
|
||||
const products_category = computed(() => {
|
||||
if (Array.isArray(slug.value) && slug.value.length >= 2) {
|
||||
return slug.value[1];
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const filters_clone = computed(() => {
|
||||
return {
|
||||
search,
|
||||
sort,
|
||||
in_stock,
|
||||
has_discount,
|
||||
slug,
|
||||
price_gte,
|
||||
price_lte,
|
||||
};
|
||||
});
|
||||
|
||||
// watch
|
||||
|
||||
watch(
|
||||
() => filters_clone.value,
|
||||
() => {
|
||||
queryClient.cancelQueries({
|
||||
queryKey: [
|
||||
QUERY_KEYS.products,
|
||||
searchDebounced,
|
||||
sort,
|
||||
in_stock,
|
||||
has_discount,
|
||||
products_category,
|
||||
price_gte,
|
||||
price_lte,
|
||||
page,
|
||||
],
|
||||
});
|
||||
page.value = 1;
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
// methods
|
||||
|
||||
const handleGetResellersProducts = async (params?: GetResellersProductsFilters) => {
|
||||
const handleGetResellersProducts = async ({ signal }: { signal: AbortSignal }) => {
|
||||
const { data } = await axios.get<GetResellersProductsResponse>(`${API_ENDPOINTS.resellers_products.get_all}`, {
|
||||
params: {
|
||||
sort: params?.sort,
|
||||
in_stock: params?.in_stock,
|
||||
search: params?.search,
|
||||
has_discount: params?.has_discount,
|
||||
category: params?.category,
|
||||
price_gte: params?.price_gte,
|
||||
price_lte: params?.price_lte,
|
||||
offset: Number(params?.page) * 15 - 15,
|
||||
sort: sort.value || "newest",
|
||||
in_stock: in_stock.value,
|
||||
search: searchDebounced.value,
|
||||
has_discount: has_discount.value,
|
||||
category: products_category.value,
|
||||
price_gte: price_gte.value,
|
||||
price_lte: price_lte.value,
|
||||
offset: Number(page.value) * 15 - 15,
|
||||
limit: 15,
|
||||
},
|
||||
signal,
|
||||
});
|
||||
|
||||
return data;
|
||||
@@ -47,8 +88,18 @@ const useGetResellersProducts = (params?: ComputedRef<GetResellersProductsFilter
|
||||
|
||||
return useQuery({
|
||||
staleTime: 60 * 1000,
|
||||
queryKey: [QUERY_KEYS.resellers_products, params],
|
||||
queryFn: () => handleGetResellersProducts(params?.value),
|
||||
queryKey: [
|
||||
QUERY_KEYS.resellers_products,
|
||||
searchDebounced,
|
||||
sort,
|
||||
in_stock,
|
||||
has_discount,
|
||||
products_category,
|
||||
price_gte,
|
||||
price_lte,
|
||||
page,
|
||||
],
|
||||
queryFn: ({ signal }) => handleGetResellersProducts({ signal }),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// imports
|
||||
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { useAppParams } from "~/composables/global/useAppParams";
|
||||
import { API_ENDPOINTS, QUERY_KEYS } from "~/constants";
|
||||
|
||||
// types
|
||||
@@ -13,19 +14,21 @@ export type GetAllTicketsRequest = {
|
||||
page: string | string[];
|
||||
};
|
||||
|
||||
const useGetAllTickets = (params: ComputedRef<GetAllTicketsRequest>) => {
|
||||
const useGetAllTickets = () => {
|
||||
// state
|
||||
|
||||
const { $axios: axios } = useNuxtApp();
|
||||
|
||||
const { sort, status, page } = useAppParams();
|
||||
|
||||
// methods
|
||||
|
||||
const handleGetAllTickets = async (params: GetAllTicketsRequest) => {
|
||||
const handleGetAllTickets = async () => {
|
||||
const { data } = await axios.get<GetAllTicketsResponse>(API_ENDPOINTS.tickets.get_all, {
|
||||
params: {
|
||||
sort: params.sort,
|
||||
filter: params.status,
|
||||
offset: Number(params.page) * 7 - 7,
|
||||
sort: sort.value ?? "created_at",
|
||||
filter: status.value,
|
||||
offset: Number(page.value) * 7 - 7,
|
||||
limit: 7,
|
||||
},
|
||||
});
|
||||
@@ -33,8 +36,8 @@ const useGetAllTickets = (params: ComputedRef<GetAllTicketsRequest>) => {
|
||||
};
|
||||
|
||||
return useQuery({
|
||||
queryKey: [QUERY_KEYS.tickets, params],
|
||||
queryFn: () => handleGetAllTickets(params.value),
|
||||
queryKey: [QUERY_KEYS.tickets, sort, status, page],
|
||||
queryFn: () => handleGetAllTickets(),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { PRODUCT_RANGE } from "~/constants";
|
||||
|
||||
export const useAppParams = () => {
|
||||
// state
|
||||
|
||||
const { y } = useWindowScroll({ behavior: "smooth" });
|
||||
|
||||
const slug = useRouteParams<string | undefined>("slug");
|
||||
|
||||
const sort = useRouteQuery<string | undefined>("sort", undefined, {
|
||||
mode: "replace",
|
||||
});
|
||||
|
||||
const search = useRouteQuery<string | undefined>("search", "", {
|
||||
mode: "replace",
|
||||
});
|
||||
|
||||
const page = useRouteQuery<number | undefined>("page", 1, {
|
||||
mode: "push",
|
||||
transform: (value) => (!!value ? +value : undefined),
|
||||
});
|
||||
|
||||
const category = useRouteQuery<string | undefined>("category", "", {
|
||||
mode: "replace",
|
||||
});
|
||||
|
||||
const status = useRouteQuery<string | undefined>("status", undefined, {
|
||||
mode: "replace",
|
||||
});
|
||||
|
||||
const price_gte = useRouteQuery<number | undefined>("price_gte", PRODUCT_RANGE.min, {
|
||||
mode: "replace",
|
||||
});
|
||||
|
||||
const price_lte = useRouteQuery<number | undefined>("price_lte", PRODUCT_RANGE.max, {
|
||||
mode: "replace",
|
||||
});
|
||||
|
||||
const in_stock = useRouteQuery<string>("in_stock", "false", {
|
||||
mode: "replace",
|
||||
});
|
||||
|
||||
const has_discount = useRouteQuery<string>("has_discount", "false", {
|
||||
mode: "replace",
|
||||
});
|
||||
|
||||
const tracking_code = useRouteQuery<string>("tracking_code", "", {
|
||||
mode: "replace",
|
||||
});
|
||||
|
||||
watch(
|
||||
() => page.value,
|
||||
() => {
|
||||
y.value = 0;
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
slug,
|
||||
sort,
|
||||
search,
|
||||
page,
|
||||
category,
|
||||
price_gte,
|
||||
price_lte,
|
||||
in_stock,
|
||||
status,
|
||||
has_discount,
|
||||
tracking_code,
|
||||
};
|
||||
};
|
||||
@@ -122,3 +122,9 @@ export const NAV_LINKS = [
|
||||
icon: "ci:call",
|
||||
},
|
||||
];
|
||||
|
||||
export const PRODUCTS_SORTS = [
|
||||
{ title: "جدیدترین ها", value: "newest" },
|
||||
{ title: "گران ترین ها", value: "price" },
|
||||
{ title: "ارزان ترین ها", value: "-price" },
|
||||
];
|
||||
|
||||
Generated
+29
@@ -20,6 +20,7 @@
|
||||
"@vuelidate/validators": "^2.0.4",
|
||||
"@vueuse/integrations": "^12.7.0",
|
||||
"@vueuse/nuxt": "^13.3.0",
|
||||
"@vueuse/router": "^13.9.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.8.1",
|
||||
"date-fns-jalali": "^4.1.0-0",
|
||||
@@ -5997,6 +5998,34 @@
|
||||
"node": ">=18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/router": {
|
||||
"version": "13.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/router/-/router-13.9.0.tgz",
|
||||
"integrity": "sha512-7AYay8Pv/0fC4D0eygbIyZuLyVs+9D7dsnO5D8aqat9qcOz91v/XFWR667WE1+p+OkU0ib+FjQUdnTVBNoIw8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vueuse/shared": "13.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.5.0",
|
||||
"vue-router": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/router/node_modules/@vueuse/shared": {
|
||||
"version": "13.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.9.0.tgz",
|
||||
"integrity": "sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/shared": {
|
||||
"version": "12.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"@vuelidate/validators": "^2.0.4",
|
||||
"@vueuse/integrations": "^12.7.0",
|
||||
"@vueuse/nuxt": "^13.3.0",
|
||||
"@vueuse/router": "^13.9.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.8.1",
|
||||
"date-fns-jalali": "^4.1.0-0",
|
||||
|
||||
@@ -16,7 +16,7 @@ definePageMeta({
|
||||
middleware: "check-is-logged-in",
|
||||
|
||||
prevPage: { name: "cart", label: "سبد خرید" },
|
||||
nextPage: { name: "cart-checkout", label: "تسویه حساب", query: "ZARINPAL" },
|
||||
nextPage: { name: "cart-checkout", label: "تسویه حساب", query: "ZIBAL" },
|
||||
});
|
||||
|
||||
// types
|
||||
|
||||
@@ -14,7 +14,7 @@ const id = route.params.id as string | undefined;
|
||||
const { suspense: suspenseProduct, data: product } = useGetProduct(id);
|
||||
|
||||
useSeoMeta({
|
||||
title: `محصول ${product.value?.name}`,
|
||||
title: product.value?.name,
|
||||
ogImage: product.value?.variants[0].images[0].image,
|
||||
twitterImage: product.value?.variants[0].images[0].image,
|
||||
ogDescription: product.value?.description,
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
// import
|
||||
|
||||
import useGetProducts, { type GetProductsFilters } from "~/composables/api/products/useGetProducts";
|
||||
import { PRODUCT_RANGE } from "~/constants";
|
||||
|
||||
// state
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
useSeoMeta({
|
||||
title: "محصولات",
|
||||
});
|
||||
// meta
|
||||
|
||||
definePageMeta({
|
||||
validate: (route) => {
|
||||
@@ -22,53 +11,36 @@ definePageMeta({
|
||||
},
|
||||
});
|
||||
|
||||
const params: GetProductsFilters = useUrlSearchParams("history", {
|
||||
removeFalsyValues: true,
|
||||
removeNullishValues: true,
|
||||
});
|
||||
// import
|
||||
|
||||
const filters = computed(() => {
|
||||
return {
|
||||
sort: params.sort ?? "newest",
|
||||
search: params.search ?? "",
|
||||
price_gte: params.price_gte ?? PRODUCT_RANGE.min,
|
||||
price_lte: params.price_lte ?? PRODUCT_RANGE.max,
|
||||
in_stock: params.in_stock ?? false,
|
||||
has_discount: params.has_discount ?? false,
|
||||
category: Array.isArray(route.params.slug) ? route.params.slug[1] ?? undefined : undefined,
|
||||
page: route.query["page"] ?? 1,
|
||||
};
|
||||
});
|
||||
import { useAppParams } from "~/composables/global/useAppParams";
|
||||
import useGetProducts from "~/composables/api/products/useGetProducts";
|
||||
|
||||
const search = ref(params.search ?? "");
|
||||
const searchDebounced = refDebounced(search, 1000);
|
||||
// state
|
||||
|
||||
// provide / inject
|
||||
|
||||
provide("params", params);
|
||||
const { search } = useAppParams();
|
||||
|
||||
// queries
|
||||
|
||||
const { data, isLoading: productsIsLoading } = useGetProducts(filters);
|
||||
const { data, isLoading: productsIsLoading } = useGetProducts();
|
||||
|
||||
// computed
|
||||
|
||||
const products = computed(() => {
|
||||
return data.value?.results.flat();
|
||||
});
|
||||
|
||||
const paginationData = computed(() => {
|
||||
return data!.value?.results.map((_, i: number) => {
|
||||
return data.value?.results.map((_, i: number) => {
|
||||
return { type: "page", value: i };
|
||||
});
|
||||
});
|
||||
|
||||
// watch
|
||||
// seo
|
||||
|
||||
watch(
|
||||
() => searchDebounced.value,
|
||||
(newValue) => {
|
||||
params.search = newValue;
|
||||
}
|
||||
);
|
||||
useSeoMeta({
|
||||
title: "محصولات",
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -133,7 +105,7 @@ watch(
|
||||
class="w-full h-max"
|
||||
>
|
||||
<div
|
||||
v-if="!products!.length"
|
||||
v-if="!products?.length"
|
||||
class="flex flex-grow w-full"
|
||||
>
|
||||
<Placeholder
|
||||
|
||||
@@ -14,16 +14,15 @@ definePageMeta({
|
||||
|
||||
import { usePushNotifications } from "~/composables/global/usePushNotifications";
|
||||
import useSubscribeNotification from "~/composables/api/notifications/useSubscribeNotification";
|
||||
import useGetAllNotifications, {
|
||||
type GetAllNotificationsRequest,
|
||||
} from "~/composables/api/account/useGetAllNotifications";
|
||||
import useGetAllNotifications from "~/composables/api/account/useGetAllNotifications";
|
||||
import { useAppParams } from "~/composables/global/useAppParams";
|
||||
|
||||
// state
|
||||
|
||||
const params: GetAllNotificationsRequest = useUrlSearchParams("history");
|
||||
|
||||
const subscribe = ref(false);
|
||||
|
||||
const { status, sort } = useAppParams();
|
||||
|
||||
const sortFilters = ref([
|
||||
{
|
||||
title: "اخبار",
|
||||
@@ -59,17 +58,9 @@ const { isPending: subscribeNotificationIsPending } = useSubscribeNotification()
|
||||
|
||||
// computeds
|
||||
|
||||
const filters = computed(() => {
|
||||
return {
|
||||
sort: params.sort ?? "created_at",
|
||||
status: params.status ?? "",
|
||||
page: params.page ?? 1,
|
||||
};
|
||||
});
|
||||
|
||||
const hasNotifications = computed(() => data.value && data.value.count > 0);
|
||||
|
||||
const { data, isLoading: notificationsIsLoading } = useGetAllNotifications(filters);
|
||||
const { data, isLoading: notificationsIsLoading } = useGetAllNotifications();
|
||||
|
||||
const notifications = computed(() => {
|
||||
return data.value?.results.flat();
|
||||
@@ -136,7 +127,7 @@ watch(
|
||||
<span class="text-xs lg:text-sm">فیلتر بر اساس</span>
|
||||
|
||||
<Select
|
||||
v-model="params.sort!"
|
||||
v-model="sort"
|
||||
triggerRootClass="!py-2.5"
|
||||
class="w-[6rem]"
|
||||
>
|
||||
@@ -169,7 +160,7 @@ watch(
|
||||
<span class="text-xs lg:text-sm">وضعیت</span>
|
||||
|
||||
<Select
|
||||
v-model="params.status!"
|
||||
v-model="status"
|
||||
triggerRootClass="!py-2.5"
|
||||
class="w-[6rem]"
|
||||
>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import useGetAllOrders, { type GetAllOrdersRequest } from "~/composables/api/orders/useGetAllOrders";
|
||||
import { useAppParams } from "~/composables/global/useAppParams";
|
||||
|
||||
// meta
|
||||
|
||||
@@ -14,15 +15,9 @@ definePageMeta({
|
||||
|
||||
// state
|
||||
|
||||
const params = useUrlSearchParams("history") as GetAllOrdersRequest;
|
||||
const route = useRoute();
|
||||
|
||||
const filters = computed(() => {
|
||||
return {
|
||||
sort: params.sort ?? "created_at",
|
||||
status: params.status ?? "",
|
||||
page: params.page ?? 1,
|
||||
};
|
||||
});
|
||||
const { sort, status } = useAppParams();
|
||||
|
||||
const tableHeads = ref(["شماره سفارش", "تاریخ ثبت", "تعداد اقلام", "مبلغ", "وضعیت", "عملیات"]);
|
||||
|
||||
@@ -72,13 +67,9 @@ const statusFilters = ref([
|
||||
},
|
||||
]);
|
||||
|
||||
// provide / inject
|
||||
|
||||
provide("params", params);
|
||||
|
||||
// queries
|
||||
|
||||
const { data, isPending: purchasesIsPending } = useGetAllOrders(filters);
|
||||
const { data, isPending: purchasesIsPending } = useGetAllOrders();
|
||||
|
||||
// computed
|
||||
|
||||
@@ -89,9 +80,9 @@ const purchases = computed(() => {
|
||||
const hasPurchases = computed(() => data.value && data.value.count > 0);
|
||||
|
||||
const hasFilters = computed(() =>
|
||||
Object.keys(params)
|
||||
Object.keys(route.query)
|
||||
.filter((key) => key != "page")
|
||||
.some((key) => (params as any)[key] != undefined)
|
||||
.some((key) => (route.query as any)[key] != undefined)
|
||||
);
|
||||
|
||||
const paginationData = computed(() => {
|
||||
@@ -103,8 +94,8 @@ const paginationData = computed(() => {
|
||||
// methods
|
||||
|
||||
const clearFilters = () => {
|
||||
params.sort = undefined;
|
||||
params.status = undefined;
|
||||
sort.value = "";
|
||||
status.value = "";
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -123,7 +114,7 @@ const clearFilters = () => {
|
||||
>
|
||||
<span class="text-xs lg:text-sm">ترتیب بر اساس</span>
|
||||
<Select
|
||||
v-model="params.sort!"
|
||||
v-model="sort"
|
||||
triggerRootClass="!py-2.5"
|
||||
class="w-[6rem]"
|
||||
>
|
||||
@@ -156,7 +147,7 @@ const clearFilters = () => {
|
||||
>
|
||||
<span class="text-xs lg:text-sm">وضعیت</span>
|
||||
<Select
|
||||
v-model="params.status!"
|
||||
v-model="status"
|
||||
triggerRootClass="!py-2.5"
|
||||
class="w-[6rem]"
|
||||
>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
// imports
|
||||
|
||||
import useGetAllTickets, { type GetAllTicketsRequest } from "~/composables/api/tickets/useGetAllTickets";
|
||||
import useGetAllTickets from "~/composables/api/tickets/useGetAllTickets";
|
||||
import { useAppParams } from "~/composables/global/useAppParams";
|
||||
|
||||
// meta
|
||||
|
||||
@@ -16,20 +17,9 @@ definePageMeta({
|
||||
|
||||
// state
|
||||
|
||||
const params: GetAllTicketsRequest = useUrlSearchParams("history", {
|
||||
initialValue: {
|
||||
page: 1,
|
||||
},
|
||||
writeMode: "push",
|
||||
});
|
||||
const route = useRoute();
|
||||
|
||||
const filters = computed(() => {
|
||||
return {
|
||||
sort: params.sort ?? "created_at",
|
||||
status: params.status ?? "",
|
||||
page: params.page ?? 1,
|
||||
};
|
||||
});
|
||||
const { sort, status } = useAppParams();
|
||||
|
||||
const tableHeads = ref(["دسته بندی", "موضوع", "تاریخ ایجاد و بروز رسانی", "وضعیت", "عملیات"]);
|
||||
|
||||
@@ -59,13 +49,9 @@ const statusFilters = ref([
|
||||
},
|
||||
]);
|
||||
|
||||
// provide / inject
|
||||
|
||||
provide("params", params);
|
||||
|
||||
// queries
|
||||
|
||||
const { data, isPending: ticketsIsPending } = useGetAllTickets(filters);
|
||||
const { data, isPending: ticketsIsPending } = useGetAllTickets();
|
||||
|
||||
// computed
|
||||
|
||||
@@ -76,9 +62,9 @@ const tickets = computed(() => {
|
||||
const hasTickets = computed(() => data.value && data.value.count > 0);
|
||||
|
||||
const hasFilters = computed(() =>
|
||||
Object.keys(params)
|
||||
Object.keys(route.query)
|
||||
.filter((key) => key != "page")
|
||||
.some((key) => (params as any)[key] != undefined)
|
||||
.some((key) => (route.query as any)[key] != undefined)
|
||||
);
|
||||
|
||||
const paginationData = computed(() => {
|
||||
@@ -90,8 +76,8 @@ const paginationData = computed(() => {
|
||||
// methods
|
||||
|
||||
const clearFilters = () => {
|
||||
params.sort = undefined;
|
||||
params.status = undefined;
|
||||
sort.value = "";
|
||||
status.value = "";
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -110,7 +96,7 @@ const clearFilters = () => {
|
||||
>
|
||||
<span class="text-xs lg:text-sm">ترتیب بر اساس</span>
|
||||
<Select
|
||||
v-model="params.sort!"
|
||||
v-model="sort"
|
||||
triggerRootClass="!py-2.5"
|
||||
class="w-[6rem]"
|
||||
>
|
||||
@@ -143,7 +129,7 @@ const clearFilters = () => {
|
||||
>
|
||||
<span class="text-xs lg:text-sm">وضعیت</span>
|
||||
<Select
|
||||
v-model="params.status!"
|
||||
v-model="status"
|
||||
triggerRootClass="!py-2.5"
|
||||
class="w-[6rem]"
|
||||
>
|
||||
|
||||
@@ -1,76 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
// import
|
||||
|
||||
import useGetResellersProducts, {
|
||||
type GetResellersProductsFilters,
|
||||
} from "~/composables/api/resellers/useGetResellersProducts";
|
||||
import { PRODUCT_RANGE } from "~/constants";
|
||||
|
||||
// state
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
useSeoMeta({
|
||||
title: "محصولات",
|
||||
});
|
||||
// meta
|
||||
|
||||
definePageMeta({
|
||||
validate: (route) => {
|
||||
if (Array.isArray(route.params.slug)) {
|
||||
return route.params.slug.length === 2 && route.params.slug[0] === "category";
|
||||
return route.params.slug.length === 2 && route.params.slug[0].startsWith("category");
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
const params: GetResellersProductsFilters = useUrlSearchParams("history", {
|
||||
removeFalsyValues: true,
|
||||
removeNullishValues: true,
|
||||
});
|
||||
// import
|
||||
|
||||
const filters = computed(() => {
|
||||
return {
|
||||
sort: params.sort ?? "newest",
|
||||
search: params.search ?? "",
|
||||
price_gte: params.price_gte ?? PRODUCT_RANGE.min,
|
||||
price_lte: params.price_lte ?? PRODUCT_RANGE.max,
|
||||
in_stock: params.in_stock ?? false,
|
||||
has_discount: params.has_discount ?? false,
|
||||
category: Array.isArray(route.params.slug) ? route.params.slug[1] ?? undefined : undefined,
|
||||
page: params.page ?? 1,
|
||||
};
|
||||
});
|
||||
import { useAppParams } from "~/composables/global/useAppParams";
|
||||
import useGetResellersProducts from "~/composables/api/resellers/useGetResellersProducts";
|
||||
|
||||
const search = ref(params.search ?? "");
|
||||
const searchDebounced = refDebounced(search, 1000);
|
||||
// state
|
||||
|
||||
// provide / inject
|
||||
|
||||
provide("params", params);
|
||||
const { search } = useAppParams();
|
||||
|
||||
// queries
|
||||
|
||||
const { data, isLoading: productsIsLoading } = useGetResellersProducts(filters);
|
||||
const { data, isLoading: productsIsLoading } = useGetResellersProducts();
|
||||
|
||||
// computed
|
||||
|
||||
const products = computed(() => {
|
||||
return data.value?.results.flat();
|
||||
});
|
||||
|
||||
const paginationData = computed(() => {
|
||||
return data!.value?.results.map((_, i: number) => {
|
||||
return data.value?.results.map((_, i: number) => {
|
||||
return { type: "page", value: i };
|
||||
});
|
||||
});
|
||||
|
||||
// watch
|
||||
// seo
|
||||
|
||||
watch(
|
||||
() => searchDebounced.value,
|
||||
(newValue) => {
|
||||
params.search = newValue;
|
||||
}
|
||||
);
|
||||
useSeoMeta({
|
||||
title: "محصولات",
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
Reference in New Issue
Block a user