Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -32,7 +32,7 @@ UNFOLD = {
|
|||||||
lambda request: static("rtl.css"),
|
lambda request: static("rtl.css"),
|
||||||
],
|
],
|
||||||
|
|
||||||
"BORDER_RADIUS": "20px",
|
"BORDER_RADIUS": "8px",
|
||||||
"SHOW_HISTORY": True,
|
"SHOW_HISTORY": True,
|
||||||
"SHOW_VIEW_ON_SITE": True,
|
"SHOW_VIEW_ON_SITE": True,
|
||||||
"ENVIRONMENT": "core.settings.environment_callback",
|
"ENVIRONMENT": "core.settings.environment_callback",
|
||||||
@@ -47,7 +47,7 @@ UNFOLD = {
|
|||||||
"500": "115 115 115",
|
"500": "115 115 115",
|
||||||
"600": "82 82 82",
|
"600": "82 82 82",
|
||||||
"700": "64 64 64",
|
"700": "64 64 64",
|
||||||
"800": "38 38 38",
|
"800": "42 42 42",
|
||||||
"900": "23 23 23",
|
"900": "23 23 23",
|
||||||
"950": "10 10 10"
|
"950": "10 10 10"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,88 +1,51 @@
|
|||||||
@layer base {
|
@layer base {
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "IRANYekanXVF";
|
font-family: "Peyda";
|
||||||
src: url("./fonts/IranYekanX/IRANYekanX-Thin.woff2");
|
src: url("./fonts/peyda/300-PeydaWeb-Light-fanum.woff2");
|
||||||
font-weight: 100;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "IRANYekanXVF";
|
|
||||||
src: url("./fonts/IranYekanX/IRANYekanX-UltraLight.woff2");
|
|
||||||
font-weight: 200;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "IRANYekanXVF";
|
|
||||||
src: url("./fonts/IranYekanX/IRANYekanX-Light.woff2");
|
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "IRANYekanXVF";
|
font-family: "Peyda";
|
||||||
src: url("./fonts/IranYekanX/IRANYekanX-Regular.woff2");
|
src: url("./fonts/peyda/400-PeydaWeb-Regular-fanum.woff2");
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "IRANYekanXVF";
|
font-family: "Peyda";
|
||||||
src: url("./fonts/IranYekanX/IRANYekanX-Medium.woff2");
|
src: url("./fonts/peyda/500-PeydaWeb-Medium-fanum.woff2");
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "IRANYekanXVF";
|
font-family: "Peyda";
|
||||||
src: url("./fonts/IranYekanX/IRANYekanX-DemiBold.woff2");
|
src: url("./fonts/peyda/600-PeydaWeb-SemiBold-fanum.woff2");
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "IRANYekanXVF";
|
font-family: "Peyda";
|
||||||
src: url("./fonts/IranYekanX/IRANYekanX-Bold.woff2");
|
src: url("./fonts/peyda/700-PeydaWeb-Bold-fanum.woff2");
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "IRANYekanXVF";
|
|
||||||
src: url("./fonts/IranYekanX/IRANYekanX-ExtraBold.woff2");
|
|
||||||
font-weight: 800;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "IRANYekanXVF";
|
font-family: "Peyda";
|
||||||
src: url("./fonts/IranYekanX/IRANYekanX-Black.woff2");
|
src: url("./fonts/peyda/900-PeydaWeb-Black-fanum.woff2");
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "IRANYekanXVF";
|
|
||||||
src: url("./fonts/IranYekanX/IRANYekanX-ExtraBlack.woff2");
|
|
||||||
font-weight: 950;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "IRANYekanXVF";
|
|
||||||
src: url("./fonts/IranYekanX/IRANYekanX-Heavy.woff2");
|
|
||||||
font-weight: 1000;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,88 @@
|
|||||||
|
@layer base {
|
||||||
|
@font-face {
|
||||||
|
font-family: "IRANYekanXVF";
|
||||||
|
src: url("./fonts/IranYekanX/IRANYekanX-Thin.woff2");
|
||||||
|
font-weight: 100;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "IRANYekanXVF";
|
||||||
|
src: url("./fonts/IranYekanX/IRANYekanX-UltraLight.woff2");
|
||||||
|
font-weight: 200;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "IRANYekanXVF";
|
||||||
|
src: url("./fonts/IranYekanX/IRANYekanX-Light.woff2");
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "IRANYekanXVF";
|
||||||
|
src: url("./fonts/IranYekanX/IRANYekanX-Regular.woff2");
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "IRANYekanXVF";
|
||||||
|
src: url("./fonts/IranYekanX/IRANYekanX-Medium.woff2");
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "IRANYekanXVF";
|
||||||
|
src: url("./fonts/IranYekanX/IRANYekanX-DemiBold.woff2");
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "IRANYekanXVF";
|
||||||
|
src: url("./fonts/IranYekanX/IRANYekanX-Bold.woff2");
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "IRANYekanXVF";
|
||||||
|
src: url("./fonts/IranYekanX/IRANYekanX-ExtraBold.woff2");
|
||||||
|
font-weight: 800;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "IRANYekanXVF";
|
||||||
|
src: url("./fonts/IranYekanX/IRANYekanX-Black.woff2");
|
||||||
|
font-weight: 900;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "IRANYekanXVF";
|
||||||
|
src: url("./fonts/IranYekanX/IRANYekanX-ExtraBlack.woff2");
|
||||||
|
font-weight: 950;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "IRANYekanXVF";
|
||||||
|
src: url("./fonts/IranYekanX/IRANYekanX-Heavy.woff2");
|
||||||
|
font-weight: 1000;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
*:not(span){
|
*:not(span){
|
||||||
font-family: 'IRANYekanXVF' !important;
|
font-family: 'Peyda' !important;
|
||||||
}
|
}
|
||||||
@@ -50,6 +50,7 @@ class ProductVariantSerialzier(serializers.ModelSerializer):
|
|||||||
images = ProductImageSerailizer(many=True)
|
images = ProductImageSerailizer(many=True)
|
||||||
details = ProductDetailSerializer(many=True, read_only=True)
|
details = ProductDetailSerializer(many=True, read_only=True)
|
||||||
cart_quantity = serializers.SerializerMethodField()
|
cart_quantity = serializers.SerializerMethodField()
|
||||||
|
price = serializers.SerializerMethodField()
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ProductVariant
|
model = ProductVariant
|
||||||
exclude = ('min_price', 'sell', 'currency', 'product', 'input_price')
|
exclude = ('min_price', 'sell', 'currency', 'product', 'input_price')
|
||||||
@@ -72,6 +73,8 @@ class ProductVariantSerialzier(serializers.ModelSerializer):
|
|||||||
return item['quantity']
|
return item['quantity']
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def get_price(self, obj):
|
||||||
|
return f'{obj.price:,.0f} تومان'
|
||||||
|
|
||||||
|
|
||||||
class SubCategorySerializer(serializers.ModelSerializer):
|
class SubCategorySerializer(serializers.ModelSerializer):
|
||||||
|
|||||||
@@ -112,7 +112,9 @@ const handleDeleteAddress = (id: number) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-end w-full lg:w-3/12">
|
<div class="flex items-center justify-end w-full lg:w-3/12">
|
||||||
<AddressModal :address="address" />
|
<ClientOnly>
|
||||||
|
<AddressModal :address="address" />
|
||||||
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const onSwiper = (swiper: SwiperClass) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="w-full flex flex-col gap-10 md:gap-[4rem]">
|
<section class="w-full flex flex-col gap-10 md:gap-[4rem] lg:container">
|
||||||
<div class="w-full flex justify-between items-center max-lg:container">
|
<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">
|
<span class="text-black typo-h-6 md:typo-h-5 lg:typo-h-4">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
@@ -91,11 +91,11 @@ const onSwiper = (swiper: SwiperClass) => {
|
|||||||
:breakpoints="{
|
:breakpoints="{
|
||||||
640: {
|
640: {
|
||||||
centeredSlides: true,
|
centeredSlides: true,
|
||||||
slidesPerView: 2.5,
|
slidesPerView: 3,
|
||||||
},
|
},
|
||||||
1024: {
|
1024: {
|
||||||
centeredSlides: false,
|
centeredSlides: false,
|
||||||
slidesPerView: 3,
|
slidesPerView: 4,
|
||||||
},
|
},
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<li
|
||||||
|
class="w-full rounded-xl border border-slate-200 bg-slate-50 p-4 flex items-center justify-between"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<Icon name="bi:info-circle" size="20" />
|
||||||
|
<h3 class="typo-sub-h-lg font-semibold">تغییر وضعیت سفارش</h3>
|
||||||
|
|
|
||||||
|
<span class="typo-p-xs text-cyan-500 font-semibold">
|
||||||
|
۲۳ تیر
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="typo-p-sm text-slate-700 text-justify">
|
||||||
|
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با
|
||||||
|
استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله
|
||||||
|
در ستون و سطرآنچنان که لازم است، و برای شرایط فعلی تکنولوژی مورد
|
||||||
|
نیاز، و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد،
|
||||||
|
کتابهای زیادی در شصت.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// imports
|
||||||
|
|
||||||
|
import { useMutation } from "@tanstack/vue-query";
|
||||||
|
import { API_ENDPOINTS } from "~/constants";
|
||||||
|
|
||||||
|
// types
|
||||||
|
|
||||||
|
export type SubscribeNotificationRequest = {
|
||||||
|
body: PushSubscriptionJSON;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useSubscribeNotification = () => {
|
||||||
|
// state
|
||||||
|
|
||||||
|
const { $axios: axios } = useNuxtApp();
|
||||||
|
|
||||||
|
// methods
|
||||||
|
|
||||||
|
const handleSubscribeNotification = async (
|
||||||
|
params: SubscribeNotificationRequest
|
||||||
|
) => {
|
||||||
|
const { data } = await axios.post(API_ENDPOINTS.account.subscribe, {
|
||||||
|
...params.body,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (subscribeData: SubscribeNotificationRequest) =>
|
||||||
|
handleSubscribeNotification(subscribeData),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useSubscribeNotification;
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
// composables/usePersianTimeAgo.ts
|
// composables/usePersianTimeAgo.ts
|
||||||
import { ref, onMounted, onUnmounted } from "vue";
|
|
||||||
import { formatDistance, toDate } from "date-fns-jalali";
|
import { formatDistance, toDate } from "date-fns-jalali";
|
||||||
import { faIR } from "date-fns-jalali/locale";
|
import { faIR } from "date-fns-jalali/locale";
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const API_ENDPOINTS = {
|
|||||||
delete: "/accounts/address/delete",
|
delete: "/accounts/address/delete",
|
||||||
},
|
},
|
||||||
update: "/accounts/profile",
|
update: "/accounts/profile",
|
||||||
|
subscribe: "/accounts/subscribe",
|
||||||
},
|
},
|
||||||
product: {
|
product: {
|
||||||
comments: "/products/comments",
|
comments: "/products/comments",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const prevPage = computed(() => route.meta.prevPage as { name: string, label: st
|
|||||||
|
|
||||||
// queries
|
// queries
|
||||||
|
|
||||||
const { data: cart, isPending: cartIsPending, suspense } = useGetCartOrders();
|
const { data: cart, isLoading: cartIsLoading, suspense } = useGetCartOrders();
|
||||||
|
|
||||||
await suspense();
|
await suspense();
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ const hasCartItem = computed(
|
|||||||
>
|
>
|
||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
</div>
|
</div>
|
||||||
<CartSummary v-if="hasCartItem && !cartIsPending" />
|
<CartSummary v-if="hasCartItem && !cartIsLoading" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ProductsSlider title="دیگر محصولات" />
|
<ProductsSlider title="دیگر محصولات" />
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ await suspense();
|
|||||||
>
|
>
|
||||||
<Header />
|
<Header />
|
||||||
<main
|
<main
|
||||||
class="w-full overflow-x-hidden container flex items-start gap-8 lg:gap-6"
|
class="w-full overflow-x-hidden container flex items-start gap-8 lg:gap-6 min-h-svh"
|
||||||
>
|
>
|
||||||
<ProfileSidebar />
|
<ProfileSidebar />
|
||||||
<div class="w-9/12">
|
<div class="w-9/12">
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
"vue-skeletor": "^1.0.6",
|
"vue-skeletor": "^1.0.6",
|
||||||
"vue3-marquee": "^4.2.2",
|
"vue3-marquee": "^4.2.2",
|
||||||
"vue3-persian-datetime-picker": "^1.2.2",
|
"vue3-persian-datetime-picker": "^1.2.2",
|
||||||
|
"web-push": "^3.6.7",
|
||||||
"workbox-window": "^7.3.0"
|
"workbox-window": "^7.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -15,9 +15,7 @@ definePageMeta({
|
|||||||
|
|
||||||
// queries
|
// queries
|
||||||
|
|
||||||
const { data: cart, isLoading: cartIsLoading, suspense } = useGetCartOrders();
|
const { data: cart, isLoading: cartIsLoading } = useGetCartOrders();
|
||||||
|
|
||||||
await suspense();
|
|
||||||
|
|
||||||
// computed
|
// computed
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,127 @@ definePageMeta({
|
|||||||
middleware: "check-is-logged-in",
|
middleware: "check-is-logged-in",
|
||||||
layout: "profile",
|
layout: "profile",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// imports
|
||||||
|
|
||||||
|
import { usePushNotifications } from "~/composables/global/usePushNotifications";
|
||||||
|
import useSubscribeNotification from "~/composables/api/notifications/useSubscribeNotification";
|
||||||
|
|
||||||
|
// state
|
||||||
|
|
||||||
|
const params = useUrlSearchParams("history");
|
||||||
|
|
||||||
|
const subscribe = ref(false);
|
||||||
|
|
||||||
|
const sortFilters = ref([
|
||||||
|
{
|
||||||
|
title: "جدید ترین",
|
||||||
|
value: "created_at",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "قدیمی ترین",
|
||||||
|
value: "-created_at",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// queries
|
||||||
|
|
||||||
|
const {
|
||||||
|
isSupported,
|
||||||
|
subscribe: notificationSubsribe,
|
||||||
|
unsubscribe: notificationUnSubsribe,
|
||||||
|
subscription,
|
||||||
|
} = usePushNotifications();
|
||||||
|
|
||||||
|
const { isPending: subscribeNotificationIsPending } =
|
||||||
|
useSubscribeNotification();
|
||||||
|
|
||||||
|
// watch
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => subscribe.value,
|
||||||
|
(nv) => {
|
||||||
|
if (!!subscription && nv) {
|
||||||
|
notificationSubsribe();
|
||||||
|
} else {
|
||||||
|
notificationUnSubsribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full flex flex-col gap-5">
|
<section class="w-full flex flex-col gap-5">
|
||||||
<ProfilePageTitle title="اعلان های شما" icon="bi:bell" />
|
<ProfilePageTitle title="اعلان های شما" icon="bi:bell" />
|
||||||
</div>
|
|
||||||
|
<div
|
||||||
|
v-if="isSupported"
|
||||||
|
class="w-fill flex items-center justify-between p-5 pt-0 border-b border-slate-200"
|
||||||
|
>
|
||||||
|
<div class="flex items-start justify-start gap-3">
|
||||||
|
<Icon name="bi:bell" size="20" />
|
||||||
|
<div class="flex flex-col gap-1 pb-0.5">
|
||||||
|
<span class="text-sm"> دریافت مستقیم اعلانات </span>
|
||||||
|
<span class="text-xs text-slate-500">
|
||||||
|
اعلانات حساب شما به صورت مستقیم به دستگاه شما ارسال می
|
||||||
|
شود
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-end gap-3">
|
||||||
|
<Switch v-model="subscribe" />
|
||||||
|
<Icon
|
||||||
|
v-if="subscribeNotificationIsPending"
|
||||||
|
name="svg-spinners:180-ring-with-bg"
|
||||||
|
size="20"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full flex items-center justify-between px-5">
|
||||||
|
<span> 1 اعلان </span>
|
||||||
|
<div class="flex items-center justify-start gap-3">
|
||||||
|
<span class="text-sm">فیلتر بر اساس</span>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
v-model="params.sort!"
|
||||||
|
triggerRootClass="!py-2.5"
|
||||||
|
class="w-[6rem]"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem
|
||||||
|
v-for="(category, index) in sortFilters"
|
||||||
|
:key="index"
|
||||||
|
class="text-xs leading-none w-full rounded-sm py-5 flex items-center justify-between h-[25px] pr-[12px] relative select-none data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-slate-300 data-[highlighted]:text-black"
|
||||||
|
:value="category.value"
|
||||||
|
>
|
||||||
|
<SelectItemIndicator
|
||||||
|
class="absolute left-0 w-[25px] inline-flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<Icon name="bi:check" size="20" />
|
||||||
|
</SelectItemIndicator>
|
||||||
|
<SelectItemText
|
||||||
|
class="text-end font-iran-yekan-x text-sm"
|
||||||
|
>
|
||||||
|
{{ category.title }}
|
||||||
|
</SelectItemText>
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</template>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full flex flex-col gap-5">
|
||||||
|
<Notification v-for="i in 3" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div v-if="data?.count > 7" class="w-full flex-center py-10">
|
||||||
|
<Pagination :items="paginationData" :total="data?.count" />
|
||||||
|
</div> -->
|
||||||
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import webPush from "web-push";
|
||||||
|
|
||||||
|
export default defineEventHandler(() => {
|
||||||
|
// Generate once and set in .env for production
|
||||||
|
if (process.env.NODE_ENV === "production") {
|
||||||
|
if (
|
||||||
|
!process.env.VAPID_PRIVATE_KEY ||
|
||||||
|
!process.env.NUXT_PUBLIC_VAPID_KEY
|
||||||
|
) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 500,
|
||||||
|
statusMessage: "VAPID keys not configured in production",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return { publicKey: process.env.NUXT_PUBLIC_VAPID_KEY };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Development: Generate and reuse
|
||||||
|
if (!process.env.VAPID_PRIVATE_KEY) {
|
||||||
|
const vapidKeys = webPush.generateVAPIDKeys();
|
||||||
|
process.env.VAPID_PRIVATE_KEY = vapidKeys.privateKey;
|
||||||
|
process.env.NUXT_PUBLIC_VAPID_KEY = vapidKeys.publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { publicKey: process.env.NUXT_PUBLIC_VAPID_KEY };
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user