تنوع رنگی :
diff --git a/frontend/components/products/FilterProducts.vue b/frontend/components/products/FilterProducts.vue
index 92c41ae..f21c9e4 100644
--- a/frontend/components/products/FilterProducts.vue
+++ b/frontend/components/products/FilterProducts.vue
@@ -83,8 +83,9 @@ const resetFilters = () => {
sliderValue.value = [PRODUCT_RANGE.min, PRODUCT_RANGE.max];
has_discount.value = false;
in_stock.value = false;
+ params.page = 1;
- router.push({ path: `/products/`, query: { ...route.query } });
+ router.push({ path: `/products/`, query: { ...route.query, page: 1 } });
};
watch(
diff --git a/frontend/composables/api/home/useHomeData.ts b/frontend/composables/api/home/useHomeData.ts
index a7fec35..e06c00f 100644
--- a/frontend/composables/api/home/useHomeData.ts
+++ b/frontend/composables/api/home/useHomeData.ts
@@ -7,37 +7,39 @@ import type { GetArticleResponse } from "~/composables/api/blog/useGetArticle";
// types
export type GetHomeDataResponse = {
- "sliders": {
- "id": number,
- "link": string,
- "title": string,
- "description": string,
- "image": string | null,
- "video": string | null
- }[],
- "main_categories": Category[],
- "products": ProductListItem[],
- "difreance_section": {
- "image1": string,
- "image2": string,
- "title1": string,
- "title2": string,
- "description1": string,
- "description2": string,
- "link1": string,
- "link2": string
- },
- "show_case_slider" : {
- "id": number,
- "title": string,
- "description": string,
- "link": string,
- "image": string,
- }[]
+ sliders: {
+ id: number;
+ link: string;
+ title: string;
+ description: string;
+ image: string | null;
+ video: string | null;
+ }[];
+ main_categories: Category[];
+ products: ProductListItem[];
+ difreance_section: {
+ image1: string;
+ image2: string;
+ title1: string;
+ title2: string;
+ description1: string;
+ description2: string;
+ link1: string;
+ link2: string;
+ };
+ show_case_slider: {
+ id: number;
+ title: string;
+ description: string;
+ link: string;
+ image1: string;
+ image2: string;
+ image3: string;
+ background_image: string;
+ }[];
};
const useHomeData = () => {
-
// state
const { $axios: axios } = useNuxtApp();
@@ -51,7 +53,7 @@ const useHomeData = () => {
return useQuery({
queryKey: [QUERY_KEYS.home],
- queryFn: () => handleHomeData()
+ queryFn: () => handleHomeData(),
});
};
diff --git a/frontend/composables/api/orders/useGetCartOrders.ts b/frontend/composables/api/orders/useGetCartOrders.ts
index 03ebe0a..c5c35f2 100644
--- a/frontend/composables/api/orders/useGetCartOrders.ts
+++ b/frontend/composables/api/orders/useGetCartOrders.ts
@@ -2,6 +2,7 @@
import { useQuery } from "@tanstack/vue-query";
import { API_ENDPOINTS, QUERY_KEYS } from "~/constants";
+import { useAuth } from "../auth/useAuth";
// types
@@ -12,17 +13,18 @@ const useGetCartOrders = () => {
const { $axios: axios } = useNuxtApp();
+ const { token } = useAuth();
+
// methods
const handleGetCartOrders = async () => {
- const { data } = await axios.get
(
- API_ENDPOINTS.orders.cart.get_all
- );
+ const { data } = await axios.get(API_ENDPOINTS.orders.cart.get_all);
return data;
};
return useQuery({
queryKey: [QUERY_KEYS.cart],
+ enabled: !!token.value,
queryFn: () => handleGetCartOrders(),
});
};
diff --git a/frontend/composables/api/tickets/useGetAllTickets.ts b/frontend/composables/api/tickets/useGetAllTickets.ts
index 2fb48d4..1ef9e67 100644
--- a/frontend/composables/api/tickets/useGetAllTickets.ts
+++ b/frontend/composables/api/tickets/useGetAllTickets.ts
@@ -21,17 +21,14 @@ const useGetAllTickets = (params: ComputedRef) => {
// methods
const handleGetAllTickets = async (params: GetAllTicketsRequest) => {
- const { data } = await axios.get(
- API_ENDPOINTS.tickets.get_all,
- {
- params: {
- sort: params.sort,
- filter: params.status,
- offset: Number(params.page) * 10 - 10,
- limit: 10,
- },
- }
- );
+ const { data } = await axios.get(API_ENDPOINTS.tickets.get_all, {
+ params: {
+ sort: params.sort,
+ filter: params.status,
+ offset: Number(params.page) * 7 - 7,
+ limit: 7,
+ },
+ });
return data;
};
diff --git a/frontend/composables/global/useSlider.ts b/frontend/composables/global/useSlider.ts
new file mode 100644
index 0000000..6e11af2
--- /dev/null
+++ b/frontend/composables/global/useSlider.ts
@@ -0,0 +1,91 @@
+type Props = {
+ duration?: number;
+ count: number;
+};
+
+const useSlider = ({ duration = 0, count }: Props) => {
+ // states
+
+ const sliderTimer = ref(null);
+ const progressTimer = ref(null);
+
+ const progress = ref(0);
+ const activeSlide = ref(0);
+
+ // methods
+
+ const slideTo = (index: number) => {
+ activeSlide.value = index;
+ restartSliderTimer();
+ };
+
+ const nextSlide = () => {
+ if (activeSlide.value > count - 2) {
+ activeSlide.value = 0;
+ } else {
+ activeSlide.value = activeSlide.value + 1;
+ }
+ };
+
+ const prevSlide = () => {
+ if (activeSlide.value < 1) {
+ activeSlide.value = count - 1;
+ } else {
+ activeSlide.value = activeSlide.value - 1;
+ }
+ };
+
+ const prevSlideHandler = () => {
+ restartSliderTimer();
+ runProgress();
+ prevSlide();
+ };
+
+ const nextSlideHandler = () => {
+ restartSliderTimer();
+ runProgress();
+ nextSlide();
+ };
+
+ const runProgress = () => {
+ const delay = duration / 100;
+
+ if (progressTimer.value) clearInterval(progressTimer.value);
+ progress.value = 0;
+
+ progressTimer.value = setInterval(() => {
+ if (progress.value < 100) {
+ progress.value = progress.value + 1;
+ }
+ }, delay);
+ };
+
+ const restartSliderTimer = () => {
+ if (sliderTimer.value) clearInterval(sliderTimer.value);
+ runProgress();
+
+ if (duration > 0) {
+ sliderTimer.value = setInterval(() => {
+ runProgress();
+ nextSlide();
+ }, duration);
+ }
+ };
+
+ onMounted(() => {
+ restartSliderTimer();
+ });
+
+ onUnmounted(() => {});
+
+ return {
+ activeSlide,
+ progress,
+ slideTo,
+ nextSlide: nextSlideHandler,
+ prevSlide: prevSlideHandler,
+ restart: restartSliderTimer,
+ };
+};
+
+export default useSlider;
diff --git a/frontend/composables/global/useToast.ts b/frontend/composables/global/useToast.ts
index 4649bac..a68c0b0 100644
--- a/frontend/composables/global/useToast.ts
+++ b/frontend/composables/global/useToast.ts
@@ -1,14 +1,14 @@
export type ToastOptions = {
description?: string;
duration?: number;
- status?: "success" | "error" | "info" | "warning",
-}
+ status?: "success" | "error" | "info" | "warning";
+};
type Toast = {
id: number;
message: string;
- options?: ToastOptions
-}
+ options?: ToastOptions;
+};
const toasts = ref([]);
@@ -20,12 +20,12 @@ export function useToast() {
};
const destroyToast = (id: number) => {
- toasts.value = toasts.value.filter(toast => toast.id !== id);
+ toasts.value = toasts.value.filter((toast) => toast.id !== id);
};
return {
toasts,
addToast,
- destroyToast
+ destroyToast,
};
-}
\ No newline at end of file
+}
diff --git a/frontend/nuxt.config.ts b/frontend/nuxt.config.ts
index b3d1b9f..fd0cb10 100644
--- a/frontend/nuxt.config.ts
+++ b/frontend/nuxt.config.ts
@@ -16,6 +16,20 @@ export default defineNuxtConfig({
name: "fade",
mode: "out-in",
},
+ head: {
+ meta: [
+ {
+ name: "mobile-web-app-capable",
+ content: "yes",
+ },
+ ],
+ link: [
+ {
+ rel: "apple-touch-icon",
+ href: "/logo/apple-icon-180.png",
+ },
+ ],
+ },
// layoutTransition: {
// name: "fade",
// mode: "out-in",
@@ -83,14 +97,28 @@ export default defineNuxtConfig({
theme_color: "#ffffff",
icons: [
{
- src: "/logo/logo-192x192.png",
+ src: "/logo/manifest-icon-192.maskable.png",
sizes: "192x192",
type: "image/png",
+ purpose: "any",
},
{
- src: "/logo/logo-512x512.png",
+ src: "/logo/manifest-icon-192.maskable.png",
+ sizes: "192x192",
+ type: "image/png",
+ purpose: "maskable",
+ },
+ {
+ src: "/logo/manifest-icon-512.maskable.png",
sizes: "512x512",
type: "image/png",
+ purpose: "any",
+ },
+ {
+ src: "/logo/manifest-icon-512.maskable.png",
+ sizes: "512x512",
+ type: "image/png",
+ purpose: "maskable",
},
],
},
diff --git a/frontend/package.json b/frontend/package.json
index 70881a0..587c266 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -27,7 +27,7 @@
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"@vueuse/integrations": "^12.7.0",
- "@vueuse/nuxt": "^12.7.0",
+ "@vueuse/nuxt": "^13.3.0",
"animate.css": "^4.1.1",
"axios": "^1.8.1",
"date-fns-jalali": "^4.1.0-0",
@@ -42,6 +42,7 @@
"swiper": "^11.2.6",
"universal-cookie": "^7.2.2",
"vue": "latest",
+ "vue-image-zoomer": "^2.4.4",
"vue-router": "latest",
"vue-scrollto": "^2.20.0",
"vue-skeletor": "^1.0.6",
diff --git a/frontend/pages/products/[...slug].vue b/frontend/pages/products/[...slug].vue
index 6bc1adc..ba9a558 100644
--- a/frontend/pages/products/[...slug].vue
+++ b/frontend/pages/products/[...slug].vue
@@ -36,7 +36,7 @@ const filters = computed(() => {
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,
+ page: route.query["page"] ?? 1,
};
});
@@ -153,6 +153,7 @@ watch(
diff --git a/frontend/pages/profile/tickets/index.vue b/frontend/pages/profile/tickets/index.vue
index bc375f4..2a653af 100644
--- a/frontend/pages/profile/tickets/index.vue
+++ b/frontend/pages/profile/tickets/index.vue
@@ -16,7 +16,12 @@ definePageMeta({
// state
-const params = useUrlSearchParams("history") as GetAllTicketsRequest;
+const params: GetAllTicketsRequest = useUrlSearchParams("history", {
+ initialValue: {
+ page: 1,
+ },
+ writeMode: "push",
+});
const filters = computed(() => {
return {
@@ -236,6 +241,7 @@ const clearFilters = () => {