Merge branch 'main' of https://github.com/Byeto-Company/hossein_por_shop
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// imports
|
// imports
|
||||||
|
|
||||||
|
import useDeleteDiscountCode from "~/composables/api/orders/useDeleteDiscountCode";
|
||||||
import useGetCartOrders from "~/composables/api/orders/useGetCartOrders";
|
import useGetCartOrders from "~/composables/api/orders/useGetCartOrders";
|
||||||
import useSubmitDiscountCode from "~/composables/api/orders/useSubmitDiscountCode";
|
import useSubmitDiscountCode from "~/composables/api/orders/useSubmitDiscountCode";
|
||||||
import { useToast } from "~/composables/global/useToast";
|
import { useToast } from "~/composables/global/useToast";
|
||||||
@@ -10,25 +11,31 @@ import { QUERY_KEYS } from "~/constants";
|
|||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { $queryClient: queryClient } = useNuxtApp();
|
const { $queryClient: queryClient } = useNuxtApp();
|
||||||
|
const { addToast } = useToast();
|
||||||
const discountCode = ref("");
|
|
||||||
|
|
||||||
// queries
|
// queries
|
||||||
|
|
||||||
const { data: cart, isLoading: cartIsLoading } = useGetCartOrders();
|
const { data: cart, isLoading: cartIsLoading } = useGetCartOrders();
|
||||||
|
|
||||||
const { addToast } = useToast();
|
const discountCode = ref(cart.value?.discount_code?.code || "");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mutateAsync: submitDiscountCode,
|
mutateAsync: submitDiscountCode,
|
||||||
isPending: submitDiscountCodeIsPending,
|
isPending: submitDiscountCodeIsPending,
|
||||||
} = useSubmitDiscountCode();
|
} = useSubmitDiscountCode();
|
||||||
|
|
||||||
|
const {
|
||||||
|
mutateAsync: deleteDiscountCode,
|
||||||
|
isPending: deleteDiscountCodeIsPending,
|
||||||
|
} = useDeleteDiscountCode();
|
||||||
|
|
||||||
// computed
|
// computed
|
||||||
|
|
||||||
const nextPage: ComputedRef<{ name: string; label: string } | undefined> =
|
const nextPage: ComputedRef<{ name: string; label: string } | undefined> =
|
||||||
computed(() => route.meta.nextPage);
|
computed(() => route.meta.nextPage);
|
||||||
|
|
||||||
|
const hasSubmittedDiscountCode = computed(() => !!cart.value?.discount_code);
|
||||||
|
|
||||||
// methods
|
// methods
|
||||||
|
|
||||||
const handleSubmitDiscountCode = () => {
|
const handleSubmitDiscountCode = () => {
|
||||||
@@ -50,6 +57,24 @@ const handleSubmitDiscountCode = () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDeleteDiscountCode = () => {
|
||||||
|
deleteDiscountCode(undefined, {
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.cart] });
|
||||||
|
discountCode.value = "";
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
addToast({
|
||||||
|
message: "خطایی در حذف کد تخفیف رخ داد",
|
||||||
|
options: {
|
||||||
|
status: "error",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
discountCode.value = "";
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -94,7 +119,7 @@ const handleSubmitDiscountCode = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="!!cart?.discount_code"
|
v-if="hasSubmittedDiscountCode"
|
||||||
class="flex items-center justify-between w-full text-status-error-primary text-red-700"
|
class="flex items-center justify-between w-full text-status-error-primary text-red-700"
|
||||||
>
|
>
|
||||||
<span class="max-w-1/2 text-sm"> تخفیف: </span>
|
<span class="max-w-1/2 text-sm"> تخفیف: </span>
|
||||||
@@ -119,19 +144,33 @@ const handleSubmitDiscountCode = () => {
|
|||||||
v-model="discountCode"
|
v-model="discountCode"
|
||||||
placeholder="کد تخفیف"
|
placeholder="کد تخفیف"
|
||||||
class="!py-3 !pe-2 ps-2.5 w-full !rounded-none !border-e-[0px] !rounded-s-100"
|
class="!py-3 !pe-2 ps-2.5 w-full !rounded-none !border-e-[0px] !rounded-s-100"
|
||||||
|
:disabled="hasSubmittedDiscountCode"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@click="handleSubmitDiscountCode"
|
@click="
|
||||||
|
hasSubmittedDiscountCode
|
||||||
|
? handleDeleteDiscountCode()
|
||||||
|
: 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"
|
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"
|
:disabled="
|
||||||
|
!discountCode.length ||
|
||||||
|
submitDiscountCodeIsPending ||
|
||||||
|
deleteDiscountCodeIsPending
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
v-if="submitDiscountCodeIsPending"
|
v-if="
|
||||||
|
submitDiscountCodeIsPending ||
|
||||||
|
deleteDiscountCodeIsPending
|
||||||
|
"
|
||||||
name="svg-spinners:180-ring-with-bg"
|
name="svg-spinners:180-ring-with-bg"
|
||||||
size="20"
|
size="20"
|
||||||
class="**:fill-white"
|
class="**:fill-white"
|
||||||
/>
|
/>
|
||||||
<span v-else> ثبت </span>
|
<span v-else>
|
||||||
|
{{ hasSubmittedDiscountCode ? "حذف" : "ثبت" }}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -140,11 +140,11 @@ watch(
|
|||||||
{{ data.product.category }}
|
{{ data.product.category }}
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
v-if="data.product.discount > 0"
|
v-if="data.discount > 0"
|
||||||
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"
|
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" />
|
<Icon name="bi:percent" class="size-4" />
|
||||||
{{ data.product.discount }}
|
{{ data.discount }}
|
||||||
تخفیف
|
تخفیف
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -227,15 +227,15 @@ watch(
|
|||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span
|
<span
|
||||||
v-if="data.product.discount > 0"
|
v-if="data.discount > 0"
|
||||||
class="typo-p-sm relative flex-center w-fit line-through"
|
class="typo-p-sm relative flex-center w-fit line-through"
|
||||||
>
|
>
|
||||||
{{ data.product.price }}
|
{{ data.price }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="typo-p-xl relative flex-center w-fit font-medium"
|
class="typo-p-xl relative flex-center w-fit font-medium"
|
||||||
>
|
>
|
||||||
{{ data.product.final_price }}
|
{{ data.final_price }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -279,11 +279,17 @@ watch(
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span
|
<div class="flex flex-col">
|
||||||
class="text-sm lg:text-[1.125rem] text-slate-900 font-semibold"
|
<span
|
||||||
>
|
v-if="data.discount > 0"
|
||||||
{{ data.product.price }}
|
class="typo-p-xs relative flex-center w-fit line-through"
|
||||||
</span>
|
>
|
||||||
|
{{ data.price }}
|
||||||
|
</span>
|
||||||
|
<span class="typo-p-md relative flex-center w-fit font-medium">
|
||||||
|
{{ data.final_price }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
placeholder: "وارد نشده",
|
placeholder: "وارد نشده",
|
||||||
});
|
});
|
||||||
const { variant, error, modelValue } = toRefs(props);
|
const { variant, error, modelValue, disabled } = toRefs(props);
|
||||||
|
|
||||||
// emits
|
// emits
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ const classes = computed(() => {
|
|||||||
{
|
{
|
||||||
"input-solid": variant.value === "solid",
|
"input-solid": variant.value === "solid",
|
||||||
"input-outlined": variant.value === "outlined",
|
"input-outlined": variant.value === "outlined",
|
||||||
|
"pointer-events-none opacity-80 text-slate-500": disabled.value,
|
||||||
"input-effects": !error.value,
|
"input-effects": !error.value,
|
||||||
[variant.value === "solid"
|
[variant.value === "solid"
|
||||||
? "input-solid-error"
|
? "input-solid-error"
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// imports
|
||||||
|
|
||||||
|
import { useMutation } from "@tanstack/vue-query";
|
||||||
|
import { API_ENDPOINTS } from "~/constants";
|
||||||
|
|
||||||
|
const useDeleteDiscountCode = () => {
|
||||||
|
// state
|
||||||
|
|
||||||
|
const { $axios: axios } = useNuxtApp();
|
||||||
|
|
||||||
|
// methods
|
||||||
|
|
||||||
|
const handleDeleteDiscountCode = async () => {
|
||||||
|
const { data } = await axios.delete(
|
||||||
|
API_ENDPOINTS.orders.cart.delete_discount
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: () => handleDeleteDiscountCode(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDeleteDiscountCode;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
export const usePWA = () => {
|
||||||
|
const isInstalledAsPWA = ref(false);
|
||||||
|
|
||||||
|
const checkPWAInstallation = () => {
|
||||||
|
const isStandalone = window.matchMedia(
|
||||||
|
"(display-mode: standalone)"
|
||||||
|
).matches;
|
||||||
|
const isIOSPWA = (window.navigator as any).standalone;
|
||||||
|
isInstalledAsPWA.value = isStandalone || isIOSPWA;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
checkPWAInstallation();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
isInstalledAsPWA,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -50,6 +50,7 @@ export const API_ENDPOINTS = {
|
|||||||
delete_all: "/order/cart/all",
|
delete_all: "/order/cart/all",
|
||||||
add_one: "/order/cart/item",
|
add_one: "/order/cart/item",
|
||||||
add_discount: "/order/cart/discount",
|
add_discount: "/order/cart/discount",
|
||||||
|
delete_discount: "/order/cart/discount",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,50 +1,39 @@
|
|||||||
import { Workbox } from "workbox-window";
|
import { Workbox } from "workbox-window";
|
||||||
|
import { usePWA } from "~/composables/global/usePwa";
|
||||||
|
|
||||||
export default defineNuxtPlugin(() => {
|
export default defineNuxtPlugin(() => {
|
||||||
const updateAvailable = ref(false);
|
const updateAvailable = ref(false);
|
||||||
let wb: Workbox | null = null;
|
let wb: Workbox | null = null;
|
||||||
|
|
||||||
if ("serviceWorker" in navigator) {
|
const { isInstalledAsPWA } = usePWA();
|
||||||
|
|
||||||
|
if ("serviceWorker" in navigator && isInstalledAsPWA.value) {
|
||||||
wb = new Workbox("/sw.js");
|
wb = new Workbox("/sw.js");
|
||||||
|
|
||||||
const isStandalone = window.matchMedia(
|
wb.addEventListener("waiting", () => {
|
||||||
"(display-mode: standalone)"
|
checkForUpdate();
|
||||||
).matches;
|
|
||||||
const isIOSPWA = (window.navigator as any).standalone;
|
|
||||||
const isInstalledAsPWA = isStandalone || isIOSPWA;
|
|
||||||
|
|
||||||
// Listen for messages from the service worker
|
|
||||||
navigator.serviceWorker.addEventListener("message", (event) => {
|
|
||||||
if (
|
|
||||||
event.data &&
|
|
||||||
event.data.type === "VERSION_CHECK"
|
|
||||||
) {
|
|
||||||
checkForUpdate(event.data.version);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Register the service worker and check if there's already a waiting one
|
wb.register()
|
||||||
wb.register().then((registration: any) => {
|
.then((registration: any) => {
|
||||||
if (registration.waiting) {
|
if (registration.waiting) {
|
||||||
checkForUpdate();
|
checkForUpdate();
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Service worker registration failed:", error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔹 Function to compare versions and show update modal if needed
|
const checkForUpdate = () => {
|
||||||
const checkForUpdate = (newVersion?: string) => {
|
updateAvailable.value = true;
|
||||||
const currentVersion = localStorage.getItem("pwa_version");
|
|
||||||
|
|
||||||
if (newVersion && currentVersion !== newVersion) {
|
|
||||||
updateAvailable.value = true;
|
|
||||||
localStorage.setItem("pwa_version", newVersion);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 🔹 Function to apply the update
|
|
||||||
const handleUpdate = () => {
|
const handleUpdate = () => {
|
||||||
wb?.messageSW({ type: "SKIP_WAITING" });
|
if (wb) {
|
||||||
window.location.reload();
|
wb.messageSW({ type: "SKIP_WAITING" }).then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Vendored
+5
-1
@@ -71,7 +71,7 @@ declare global {
|
|||||||
discount: number;
|
discount: number;
|
||||||
color: string;
|
color: string;
|
||||||
video: string | null;
|
video: string | null;
|
||||||
cart_quantity : number;
|
cart_quantity: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Product = {
|
type Product = {
|
||||||
@@ -215,6 +215,10 @@ declare global {
|
|||||||
discount_amount: string;
|
discount_amount: string;
|
||||||
final_price: string;
|
final_price: string;
|
||||||
};
|
};
|
||||||
|
discount: number;
|
||||||
|
discount_amount: string;
|
||||||
|
price: string;
|
||||||
|
final_price: string;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user