merge and update otp message
This commit is contained in:
@@ -1,26 +1,37 @@
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
// imports
|
||||
|
||||
import usePersianDate from "~/composables/global/usePersianDate";
|
||||
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
data: Notification;
|
||||
};
|
||||
|
||||
// props
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
// state
|
||||
|
||||
const { formatToPersian } = usePersianDate();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li
|
||||
class="w-full rounded-xl border border-slate-200 bg-slate-50 p-4 flex items-center justify-between"
|
||||
>
|
||||
<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" class="lg:text-lg" />
|
||||
<h3 class="max-lg:text-sm font-semibold">تغییر وضعیت سفارش</h3>
|
||||
<Icon
|
||||
:name="data.notif_type == 'NEWS' ? 'bi:info-circle' : 'bi:bell'"
|
||||
class="lg:text-lg"
|
||||
/>
|
||||
<h3 class="max-lg:text-sm font-semibold">{{ data.title }}</h3>
|
||||
|
|
||||
<span class="text-xs text-cyan-500 font-semibold">
|
||||
۲۳ تیر
|
||||
</span>
|
||||
<span class="text-xs text-cyan-500 font-semibold">{{ formatToPersian(data.created_at) }}</span>
|
||||
</div>
|
||||
<p
|
||||
class="text-xs lg:text-sm leading-[175%] text-slate-700 text-justify"
|
||||
>
|
||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با
|
||||
استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله
|
||||
در ستون و سطرآنچنان که لازم است، و برای شرایط فعلی تکنولوژی مورد
|
||||
نیاز، و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد،
|
||||
کتابهای زیادی در شصت.
|
||||
<p class="text-xs lg:text-sm leading-[175%] text-slate-700 text-justify">
|
||||
{{ data.content }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// imports
|
||||
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
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>) => {
|
||||
// state
|
||||
|
||||
const { $axios: axios } = useNuxtApp();
|
||||
|
||||
// methods
|
||||
|
||||
const handleGetAllNotifications = async (params: GetAllNotificationsRequest) => {
|
||||
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,
|
||||
limit: 10,
|
||||
},
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
return useQuery({
|
||||
queryKey: [QUERY_KEYS.notifications, params],
|
||||
queryFn: () => handleGetAllNotifications(params.value),
|
||||
});
|
||||
};
|
||||
|
||||
export default useGetAllNotifications;
|
||||
@@ -15,6 +15,9 @@ export const API_ENDPOINTS = {
|
||||
},
|
||||
update: "/accounts/profile",
|
||||
subscribe: "/accounts/subscribe",
|
||||
notifications: {
|
||||
get_all: "/accounts/notification/all",
|
||||
},
|
||||
},
|
||||
product: {
|
||||
comments: "/products/comments",
|
||||
@@ -85,6 +88,7 @@ export const QUERY_KEYS = {
|
||||
orders: "orders",
|
||||
cart: "cart",
|
||||
transaction: "transaction",
|
||||
notifications: "notifications",
|
||||
};
|
||||
|
||||
export const MUTATION_KEYS = {
|
||||
|
||||
@@ -32,7 +32,7 @@ type DeliveryData = {
|
||||
|
||||
// queries
|
||||
|
||||
const { data: cart, isLoading: cartIsLoading } = useGetCartOrders();
|
||||
const { data: cart } = useGetCartOrders();
|
||||
|
||||
const { data: addresses, isLoading: addressesIsLoading } = useGetAllAddress();
|
||||
|
||||
@@ -44,9 +44,6 @@ const selectedAddress = computed(() => {
|
||||
|
||||
// state
|
||||
|
||||
const { $queryClient: queryClient } = useNuxtApp();
|
||||
const { addToast } = useToast();
|
||||
|
||||
const deliveryData = ref<DeliveryData>({
|
||||
address: selectedAddress.value,
|
||||
deliveryMethod: {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// meta
|
||||
|
||||
useSeoMeta({
|
||||
title : "پنل کاربری اعلان ها"
|
||||
title: "پنل کاربری اعلان ها",
|
||||
});
|
||||
|
||||
definePageMeta({
|
||||
@@ -14,21 +14,35 @@ definePageMeta({
|
||||
|
||||
import { usePushNotifications } from "~/composables/global/usePushNotifications";
|
||||
import useSubscribeNotification from "~/composables/api/notifications/useSubscribeNotification";
|
||||
import useGetAllNotifications, {
|
||||
type GetAllNotificationsRequest,
|
||||
} from "~/composables/api/account/useGetAllNotifications";
|
||||
|
||||
// state
|
||||
|
||||
const params = useUrlSearchParams("history");
|
||||
const params: GetAllNotificationsRequest = useUrlSearchParams("history");
|
||||
|
||||
const subscribe = ref(false);
|
||||
|
||||
const sortFilters = ref([
|
||||
{
|
||||
title: "جدید ترین",
|
||||
value: "created_at",
|
||||
title: "اخبار",
|
||||
value: "NEWS",
|
||||
},
|
||||
{
|
||||
title: "قدیمی ترین",
|
||||
value: "-created_at",
|
||||
title: "اعلانات کاربر",
|
||||
value: "USER_NOTIF",
|
||||
},
|
||||
]);
|
||||
|
||||
const statusFilters = ref([
|
||||
{
|
||||
title: "خوانده شده",
|
||||
value: "read",
|
||||
},
|
||||
{
|
||||
title: "خوانده نشده",
|
||||
value: "not_read",
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -41,8 +55,31 @@ const {
|
||||
subscription,
|
||||
} = usePushNotifications();
|
||||
|
||||
const { isPending: subscribeNotificationIsPending } =
|
||||
useSubscribeNotification();
|
||||
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 notifications = computed(() => {
|
||||
return data.value?.results.flat();
|
||||
});
|
||||
|
||||
const paginationData = computed(() => {
|
||||
return data.value?.results.map((_, i: number) => {
|
||||
return { type: "page", value: i };
|
||||
});
|
||||
});
|
||||
|
||||
// watch
|
||||
|
||||
@@ -60,19 +97,24 @@ watch(
|
||||
|
||||
<template>
|
||||
<section class="w-full flex flex-col gap-5">
|
||||
<ProfilePageTitle title="اعلان های شما" icon="bi:bell" />
|
||||
<ProfilePageTitle
|
||||
title="اعلان های شما"
|
||||
icon="bi:bell"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="isSupported"
|
||||
class="w-fill flex items-center justify-between py-5 lg:p-5 pt-0 border-b border-slate-200 gap-5"
|
||||
>
|
||||
<div class="flex items-start justify-start gap-3">
|
||||
<Icon name="bi:bell" size="20" />
|
||||
<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 leading-[175%]">
|
||||
اعلانات حساب شما به صورت مستقیم به دستگاه شما ارسال می
|
||||
شود
|
||||
اعلانات حساب شما به صورت مستقیم به دستگاه شما ارسال می شود
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,47 +130,120 @@ watch(
|
||||
</div>
|
||||
|
||||
<div class="w-full flex items-center justify-between lg:px-5">
|
||||
<span class="max-lg:text-sm"> 1 اعلان </span>
|
||||
<div class="flex items-center justify-start gap-3">
|
||||
<span class="text-xs lg:text-sm">فیلتر بر اساس</span>
|
||||
<span class="whitespace-pre max-lg:hidden"> 1 اعلان </span>
|
||||
<div class="max-lg:w-full flex items-center justify-end gap-8">
|
||||
<div class="flex items-center justify-start gap-3">
|
||||
<span class="text-xs lg: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-4 lg: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"
|
||||
<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-4 lg: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"
|
||||
>
|
||||
<Icon name="bi:check" size="20" />
|
||||
</SelectItemIndicator>
|
||||
<SelectItemText
|
||||
class="text-end font-iran-yekan-x text-xs lg:text-sm"
|
||||
<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-xs lg:text-sm">
|
||||
{{ category.title }}
|
||||
</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</template>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-start gap-3">
|
||||
<span class="text-xs lg:text-sm">وضعیت</span>
|
||||
|
||||
<Select
|
||||
v-model="params.status!"
|
||||
triggerRootClass="!py-2.5"
|
||||
class="w-[6rem]"
|
||||
>
|
||||
<template #content>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="(category, index) in statusFilters"
|
||||
:key="index"
|
||||
class="text-xs leading-none w-full rounded-sm py-4 lg: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"
|
||||
>
|
||||
{{ category.title }}
|
||||
</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</template>
|
||||
</Select>
|
||||
<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-xs lg:text-sm">
|
||||
{{ category.title }}
|
||||
</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</template>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex flex-col gap-5">
|
||||
<Notification v-for="i in 3" />
|
||||
<div
|
||||
v-if="notificationsIsLoading"
|
||||
class="flex flex-col gap-6 w-full"
|
||||
>
|
||||
<Skeleton
|
||||
v-for="i in 3"
|
||||
class="w-full !h-[8rem] !rounded-xl"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- <div v-if="data?.count > 7" class="w-full flex-center py-10">
|
||||
<Pagination :items="paginationData" :total="data?.count" />
|
||||
</div> -->
|
||||
<template v-else>
|
||||
<div
|
||||
v-if="!hasNotifications && !notificationsIsLoading"
|
||||
class="flex flex-grow w-full"
|
||||
v-auto-animate
|
||||
>
|
||||
<Placeholder
|
||||
class="!w-full !py-[5rem]"
|
||||
icon="bi:ticket"
|
||||
title="اعلانی یافت نشد"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="flex flex-col gap-6 w-full"
|
||||
>
|
||||
<Notification
|
||||
v-for="(notification, index) in notifications"
|
||||
:key="index"
|
||||
:data="notification"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div
|
||||
v-if="data?.count! > 7"
|
||||
class="w-full flex-center py-10"
|
||||
>
|
||||
<Pagination
|
||||
:items="paginationData"
|
||||
:total="data?.count!"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
// imports
|
||||
|
||||
import useGetAllTickets, {
|
||||
type GetAllTicketsRequest,
|
||||
} from "~/composables/api/tickets/useGetAllTickets";
|
||||
import useGetAllTickets, { type GetAllTicketsRequest } from "~/composables/api/tickets/useGetAllTickets";
|
||||
|
||||
// meta
|
||||
|
||||
useSeoMeta({
|
||||
title : "پنل کاربری تیکت ها"
|
||||
title: "پنل کاربری تیکت ها",
|
||||
});
|
||||
|
||||
definePageMeta({
|
||||
@@ -28,13 +26,7 @@ const filters = computed(() => {
|
||||
};
|
||||
});
|
||||
|
||||
const tableHeads = ref([
|
||||
"دسته بندی",
|
||||
"موضوع",
|
||||
"تاریخ ایجاد و بروز رسانی",
|
||||
"وضعیت",
|
||||
"عملیات",
|
||||
]);
|
||||
const tableHeads = ref(["دسته بندی", "موضوع", "تاریخ ایجاد و بروز رسانی", "وضعیت", "عملیات"]);
|
||||
|
||||
const sortFilters = ref([
|
||||
{
|
||||
@@ -100,15 +92,14 @@ const clearFilters = () => {
|
||||
|
||||
<template>
|
||||
<div class="w-full flex flex-col gap-5">
|
||||
<ProfilePageTitle title="تیکت های شما" icon="bi:ticket" />
|
||||
<ProfilePageTitle
|
||||
title="تیکت های شما"
|
||||
icon="bi:ticket"
|
||||
/>
|
||||
|
||||
<div class="w-full flex flex-col gap-5">
|
||||
<div
|
||||
class="w-full flex flex-col-reverse lg:flex-row items-center justify-between lg:px-5 gap-5"
|
||||
>
|
||||
<div
|
||||
class="max-lg:w-full flex items-center justify-start gap-8"
|
||||
>
|
||||
<div class="w-full flex flex-col-reverse lg:flex-row items-center justify-between lg:px-5 gap-5">
|
||||
<div class="max-lg:w-full flex items-center justify-start gap-8">
|
||||
<div
|
||||
class="flex flex-col lg:flex-row items-start lg:items-center justify-start gap-3 max-lg:w-full"
|
||||
>
|
||||
@@ -129,11 +120,12 @@ const clearFilters = () => {
|
||||
<SelectItemIndicator
|
||||
class="absolute left-0 w-[25px] inline-flex items-center justify-center"
|
||||
>
|
||||
<Icon name="bi:check" size="20" />
|
||||
<Icon
|
||||
name="bi:check"
|
||||
size="20"
|
||||
/>
|
||||
</SelectItemIndicator>
|
||||
<SelectItemText
|
||||
class="text-end font-iran-yekan-x lg:text-sm"
|
||||
>
|
||||
<SelectItemText class="text-end font-iran-yekan-x lg:text-sm">
|
||||
{{ category.title }}
|
||||
</SelectItemText>
|
||||
</SelectItem>
|
||||
@@ -153,9 +145,7 @@ const clearFilters = () => {
|
||||
<template #content>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="(
|
||||
category, index
|
||||
) in statusFilters"
|
||||
v-for="(category, index) in statusFilters"
|
||||
:key="index"
|
||||
class="text-xs leading-none w-full rounded-sm py-4 lg: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"
|
||||
@@ -163,11 +153,12 @@ const clearFilters = () => {
|
||||
<SelectItemIndicator
|
||||
class="absolute left-0 w-[25px] inline-flex items-center justify-center"
|
||||
>
|
||||
<Icon name="bi:check" size="20" />
|
||||
<Icon
|
||||
name="bi:check"
|
||||
size="20"
|
||||
/>
|
||||
</SelectItemIndicator>
|
||||
<SelectItemText
|
||||
class="text-end font-iran-yekan-x text-xs lg:text-sm"
|
||||
>
|
||||
<SelectItemText class="text-end font-iran-yekan-x text-xs lg:text-sm">
|
||||
{{ category.title }}
|
||||
</SelectItemText>
|
||||
</SelectItem>
|
||||
@@ -217,11 +208,7 @@ const clearFilters = () => {
|
||||
:key="index"
|
||||
scope="col"
|
||||
:class="
|
||||
[0, 1, 2].includes(index)
|
||||
? 'w-3/12'
|
||||
: tableHeads.length - 1 == index
|
||||
? 'w-1/2'
|
||||
: 'w-2/12'
|
||||
[0, 1, 2].includes(index) ? 'w-3/12' : tableHeads.length - 1 == index ? 'w-1/2' : 'w-2/12'
|
||||
"
|
||||
class="px-6 py-5 text-xs lg:text-sm font-normal shrink-0 whitespace-pre"
|
||||
>
|
||||
@@ -246,7 +233,10 @@ const clearFilters = () => {
|
||||
v-if="data && paginationData && data.count > 7"
|
||||
class="w-full flex-center py-10"
|
||||
>
|
||||
<Pagination :items="paginationData" :total="data?.count" />
|
||||
<Pagination
|
||||
:items="paginationData"
|
||||
:total="data?.count"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Vendored
+8
@@ -287,4 +287,12 @@ declare global {
|
||||
status_detail: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
type Notification = {
|
||||
title: string;
|
||||
content: string;
|
||||
created_at: string;
|
||||
notif_type: "NEWS" | "USER_NOTIF";
|
||||
is_read?: string;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user