bottom navbar change
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import * as api from "../service/NotificationService";
|
import * as api from "../service/NotificationService";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
export const useGetNotifications = () => {
|
export const useGetNotifications = () => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -7,3 +7,13 @@ export const useGetNotifications = () => {
|
|||||||
queryFn: api.getNotifications,
|
queryFn: api.getNotifications,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useDeleteNotification = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: api.deleteNotification,
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["notifications"] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -5,16 +5,82 @@ import { ArrowLeft, Trash } from 'iconsax-react';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useGetNotifications } from './hooks/useNotificationData';
|
import { useGetNotifications, useDeleteNotification } from './hooks/useNotificationData';
|
||||||
|
import type { Notification } from './types/Types';
|
||||||
|
|
||||||
type Props = object
|
type Props = object
|
||||||
|
|
||||||
|
const formatDate = (dateString: string): string => {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toLocaleDateString('fa-IR', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTime = (dateString: string): string => {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toLocaleTimeString('fa-IR', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export default function NotificationsIndex({ }: Props) {
|
export default function NotificationsIndex({ }: Props) {
|
||||||
|
|
||||||
|
|
||||||
const { t } = useTranslation('notifications')
|
const { t } = useTranslation('notifications')
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { data } = useGetNotifications();
|
const { data, isLoading, isError } = useGetNotifications();
|
||||||
|
const { mutate: deleteNotification } = useDeleteNotification();
|
||||||
|
|
||||||
|
const notifications = React.useMemo(() => {
|
||||||
|
return data?.data ?? [];
|
||||||
|
}, [data?.data]);
|
||||||
|
|
||||||
|
const handleDelete = (id: string) => {
|
||||||
|
deleteNotification(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className='h-full bg-inherit relative flex flex-col lg:gap-4'>
|
||||||
|
<div className='grid grid-cols-3 items-center py-4 '>
|
||||||
|
<span></span>
|
||||||
|
<h1 className='text-sm2 place-self-center font-medium'>{t("Heading")}</h1>
|
||||||
|
<ArrowLeft
|
||||||
|
className='cursor-pointer place-self-end'
|
||||||
|
size='24'
|
||||||
|
color='currentColor'
|
||||||
|
onClick={() => { router.back() }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col items-center justify-center gap-2 py-12 text-sm2 text-muted-foreground'>
|
||||||
|
<span className='h-4 w-4 animate-spin rounded-full border border-dashed border-foreground border-t-transparent'></span>
|
||||||
|
<p>در حال دریافت اعلانها...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError || !notifications.length) {
|
||||||
|
return (
|
||||||
|
<div className='h-full bg-inherit relative flex flex-col lg:gap-4'>
|
||||||
|
<div className='grid grid-cols-3 items-center py-4 '>
|
||||||
|
<span></span>
|
||||||
|
<h1 className='text-sm2 place-self-center font-medium'>{t("Heading")}</h1>
|
||||||
|
<ArrowLeft
|
||||||
|
className='cursor-pointer place-self-end'
|
||||||
|
size='24'
|
||||||
|
color='currentColor'
|
||||||
|
onClick={() => { router.back() }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col items-center justify-center gap-2 py-12 text-sm2 text-muted-foreground'>
|
||||||
|
<p>اعلانی وجود ندارد</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='h-full bg-inherit relative flex flex-col lg:gap-4'>
|
<div className='h-full bg-inherit relative flex flex-col lg:gap-4'>
|
||||||
@@ -30,27 +96,40 @@ export default function NotificationsIndex({ }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul className='flex flex-col gap-4 pb-20'>
|
<ul className='flex flex-col gap-4 pb-20'>
|
||||||
<li className='bg-container py-5 px-4 rounded-container shadow-lg'>
|
{notifications.map((notification: Notification) => {
|
||||||
<h5 className='font-medium text-sm'>عنوان پیام</h5>
|
const isNew = !notification.sentAt;
|
||||||
<p className='text-xs mt-2 leading-5'>لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است</p>
|
const formattedDate = formatDate(notification.createdAt);
|
||||||
|
const formattedTime = formatTime(notification.createdAt);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={notification.id} className='bg-container py-5 px-4 rounded-container shadow-lg'>
|
||||||
|
<h5 className='font-medium text-sm'>{notification.title}</h5>
|
||||||
|
<p className='text-xs mt-2 leading-5'>{notification.content}</p>
|
||||||
|
|
||||||
<div className='flex items-center justify-between mt-5 '>
|
<div className='flex items-center justify-between mt-5 '>
|
||||||
<div className="flex items-center justify-start gap-2">
|
<div className="flex items-center justify-start gap-2">
|
||||||
|
{isNew && (
|
||||||
<span className='px-4 pt-1 pb-0.5 text-xs bg-primary text-white rounded-md'>
|
<span className='px-4 pt-1 pb-0.5 text-xs bg-primary text-white rounded-md'>
|
||||||
{t('Tags.New')}
|
{t('Tags.New')}
|
||||||
</span>
|
</span>
|
||||||
|
)}
|
||||||
<span className='text-disabled2 text-xs pt-0.5'>
|
<span className='text-disabled2 text-xs pt-0.5'>
|
||||||
{ef('28 مرداد 1401')}
|
{ef(formattedDate)}
|
||||||
</span>
|
</span>
|
||||||
<span className='size-1.5 bg-disabled rounded-full'></span>
|
<span className='size-1.5 bg-disabled rounded-full'></span>
|
||||||
<span className='text-disabled2 text-xs pt-0.5'>
|
<span className='text-disabled2 text-xs pt-0.5'>
|
||||||
{ef('21:32')}
|
{ef(formattedTime)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Trash size={24} className='stroke-primary dark:stroke-foreground' />
|
<Trash
|
||||||
|
size={24}
|
||||||
|
className='stroke-primary dark:stroke-foreground cursor-pointer'
|
||||||
|
onClick={() => handleDelete(notification.id)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
import { api } from "@/config/axios";
|
import { api } from "@/config/axios";
|
||||||
|
import { NotificationsResponse } from "../types/Types";
|
||||||
|
import { BaseResponse } from "@/app/[name]/(Main)/types/Types";
|
||||||
|
|
||||||
export const getNotifications = async () => {
|
export const getNotifications = async (): Promise<NotificationsResponse> => {
|
||||||
const { data } = await api.get("/public/notifications");
|
const { data } = await api.get<NotificationsResponse>(
|
||||||
|
"/public/notifications"
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteNotification = async (
|
||||||
|
id: string
|
||||||
|
): Promise<BaseResponse<unknown>> => {
|
||||||
|
const { data } = await api.delete<BaseResponse<unknown>>(
|
||||||
|
`/public/notifications/${id}`
|
||||||
|
);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { BaseResponse } from "@/app/[name]/(Main)/types/Types";
|
||||||
|
|
||||||
|
export interface Notification {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
deletedAt: string | null;
|
||||||
|
restaurant: string;
|
||||||
|
user: string;
|
||||||
|
admin: string | null;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
sentAt: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NotificationsResponse = BaseResponse<Notification[]>;
|
||||||
@@ -52,20 +52,11 @@ function BottomNavBar({ onPagerClick }: BottomNavBarProps) {
|
|||||||
{/* 🔽 Your HTML content inside SVG */}
|
{/* 🔽 Your HTML content inside SVG */}
|
||||||
<foreignObject x="0" y="10" width="100%" height="80">
|
<foreignObject x="0" y="10" width="100%" height="80">
|
||||||
<nav
|
<nav
|
||||||
className="h-full px-4 grid grid-cols-5 gap-x-1 text-[10px] items-end overflow-hidden"
|
className="h-full px-4 grid grid-cols-5 gap-x-1 text-[10px] items-end"
|
||||||
>
|
>
|
||||||
<BottomNavLink
|
<BottomNavLink
|
||||||
href={`/${name}/cart`}
|
href={`/${name}/cart`}
|
||||||
icon={
|
icon={<BagIcon width={20} height={20} />}
|
||||||
<div className="relative flex-shrink-0">
|
|
||||||
<BagIcon width={20} height={20} />
|
|
||||||
{cartItemsCount > 0 && (
|
|
||||||
<span className="absolute -top-2 -right-2 flex items-center justify-center min-w-[18px] h-[18px] px-1 text-[10px] font-medium text-white bg-primary rounded-full">
|
|
||||||
{cartItemsCount > 99 ? '99+' : cartItemsCount}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
value={t('Cart')}
|
value={t('Cart')}
|
||||||
/>
|
/>
|
||||||
{onPagerClick ? (
|
{onPagerClick ? (
|
||||||
@@ -134,6 +125,29 @@ function BottomNavBar({ onPagerClick }: BottomNavBarProps) {
|
|||||||
</filter>
|
</filter>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
|
{/* Badge Layer - خارج از SVG - دقیقاً مثل foreignObject */}
|
||||||
|
{cartItemsCount > 0 && (
|
||||||
|
<div
|
||||||
|
className="absolute left-0 right-0 pointer-events-none"
|
||||||
|
style={{
|
||||||
|
top: 'calc(10 / 104 * 100%)',
|
||||||
|
height: 'calc(80 / 104 * 100%)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="h-full px-4 grid grid-cols-5 gap-x-1 items-end">
|
||||||
|
<div className="flex flex-col items-center gap-[5px]">
|
||||||
|
<div className="relative flex-shrink-0">
|
||||||
|
<div className="w-5 h-5 opacity-0"></div>
|
||||||
|
<span className="absolute -top-1 -right-1 flex items-center justify-center min-w-[18px] h-[18px] px-1 text-[10px] font-medium text-white bg-primary rounded-full z-50">
|
||||||
|
{cartItemsCount > 99 ? '99+' : cartItemsCount}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs2 opacity-0">{t('Cart')}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user