This commit is contained in:
Parsa Nazer
2025-03-19 17:03:28 +03:30
8 changed files with 137 additions and 53 deletions
@@ -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>
+16 -10
View File
@@ -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>
+2 -1
View File
@@ -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;
+19
View File
@@ -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,
};
};
+1
View File
@@ -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",
}, },
}, },
}; };
+22 -33
View File
@@ -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 {
+5 -1
View File
@@ -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;
}; };