diff --git a/src/app/(notifications)/notification-history/[id]/page.tsx b/src/app/(notifications)/notification-history/[id]/page.tsx index 425234d..3f01c84 100644 --- a/src/app/(notifications)/notification-history/[id]/page.tsx +++ b/src/app/(notifications)/notification-history/[id]/page.tsx @@ -7,6 +7,7 @@ import { useGetNotificationDetails } from "@/hooks/queries/useNotificationQuerie import { BreadcrumbItem } from "@/types/shared"; import { unixToDate } from "@/utils/shared"; import { use } from "react"; +import { capitalize } from "../../../../utils/shared"; interface IProps { params: Promise<{ id: string }>; @@ -36,11 +37,6 @@ const NotificationDetailsPage = ({ params }: IProps) => {
- - - {data?.data.message} - - {data?.data.event_type} @@ -52,12 +48,14 @@ const NotificationDetailsPage = ({ params }: IProps) => { - {data?.data.priority} + {capitalize(data?.data.priority ?? "")} - {data?.data.type} + + {capitalize(data?.data.type ?? "")} + @@ -72,6 +70,12 @@ const NotificationDetailsPage = ({ params }: IProps) => { {data?.data.seen ? "Yes" : "No"}
+
+ + + {data?.data.message} + +
); diff --git a/src/app/loading.tsx b/src/app/loading.tsx index 2be5c92..7979c4a 100644 --- a/src/app/loading.tsx +++ b/src/app/loading.tsx @@ -3,10 +3,11 @@ import { cn } from "../lib/utils"; interface IProps { className?: string | null; } + const Loading = ({ className = "w-svw h-svh" }: IProps) => { return (
- +
); }; diff --git a/src/components/FormElements/DatePicker/DatePicker.tsx b/src/components/FormElements/DatePicker/DatePicker.tsx index 6449149..8b32c3a 100644 --- a/src/components/FormElements/DatePicker/DatePicker.tsx +++ b/src/components/FormElements/DatePicker/DatePicker.tsx @@ -93,7 +93,7 @@ const DatePicker = ({ onChange(date.isValid ? date.toUnix() : ""); } }} - format={"YYYY/MM/DD"} + format={`YYYY/MM/DD ${timePicker ? "HH:mm" : ""}`} containerClassName="w-full" inputClass={cn( "w-full rounded-lg border-[1.5px] border-stroke bg-transparent px-5.5 py-3 pl-12.5 text-dark outline-none transition focus:border-primary disabled:cursor-default disabled:bg-gray-2 dark:border-dark-3 dark:bg-dark-2 dark:text-white dark:focus:border-primary dark:disabled:bg-dark", diff --git a/src/components/Layouts/header/notification/index.tsx b/src/components/Layouts/header/notification/index.tsx index 3cc5167..0223677 100644 --- a/src/components/Layouts/header/notification/index.tsx +++ b/src/components/Layouts/header/notification/index.tsx @@ -5,125 +5,109 @@ import { DropdownContent, DropdownTrigger, } from "@/components/ui/dropdown"; +import Modal from "@/components/ui/modal"; +import { useMarkNotificationAsSeen } from "@/hooks/queries/useNotificationQueries"; import { useIsMobile } from "@/hooks/use-mobile"; +import { TNotificationDetailsResponse } from "@/lib/api/types/NotificationHistory"; // Import the type import { cn } from "@/lib/utils"; -import Image from "next/image"; import Link from "next/link"; import { useState } from "react"; import { BellIcon } from "./icons"; - -const notificationList = [ - { - image: "/images/user/user-15.png", - title: "Piter Joined the Team!", - subTitle: "Congratulate him", - }, - { - image: "/images/user/user-03.png", - title: "New message", - subTitle: "Devid sent a new message", - }, - { - image: "/images/user/user-26.png", - title: "New Payment received", - subTitle: "Check your earnings", - }, - { - image: "/images/user/user-28.png", - title: "Jolly completed tasks", - subTitle: "Assign new task", - }, - { - image: "/images/user/user-27.png", - title: "Roman Joined the Team!", - subTitle: "Congratulate him", - }, -]; +import NotificationList from "./notification-list"; export function Notification() { const [isOpen, setIsOpen] = useState(false); const [isDotVisible, setIsDotVisible] = useState(true); + const [unreadNotificationCount, setUnreadNotificationCount] = + useState(0); + const [isModalOpen, setIsModalOpen] = useState(false); + const [currentNotification, setCurrentNotification] = + useState(null); + + const { mutate } = useMarkNotificationAsSeen( + currentNotification?.id ?? "", + () => setIsModalOpen(false), + ); const isMobile = useIsMobile(); + const handleNotificationClick = ( + notification: TNotificationDetailsResponse, + ) => { + setCurrentNotification(notification); + setIsModalOpen(true); + setIsOpen(false); + }; + return ( - { - setIsOpen(open); - - if (setIsDotVisible) setIsDotVisible(false); - }} - > - + { + setIsOpen(open); + if (setIsDotVisible) setIsDotVisible(false); + }} > - - - - {isDotVisible && ( - - - - )} - - - - -
- - Notifications - - - 5 new - -
- -
    - {notificationList.map((item, index) => ( -
  • - setIsOpen(false)} - className="flex items-center gap-4 rounded-lg px-2 py-1.5 outline-none hover:bg-gray-2 focus-visible:bg-gray-2 dark:hover:bg-dark-3 dark:focus-visible:bg-dark-3" - > - User - -
    - - {item.title} - - - - {item.subTitle} - -
    - -
  • - ))} -
- - setIsOpen(false)} - className="block rounded-lg border border-primary p-2 text-center text-sm font-medium tracking-wide text-primary outline-none transition-colors hover:bg-blue-light-5 focus:bg-blue-light-5 focus:text-primary focus-visible:border-primary dark:border-dark-3 dark:text-dark-6 dark:hover:border-dark-5 dark:hover:bg-dark-3 dark:hover:text-dark-7 dark:focus-visible:border-dark-5 dark:focus-visible:bg-dark-3 dark:focus-visible:text-dark-7" + - See all notifications - -
-
+ + + {isDotVisible && ( + + + + )} + + + + +
+ + Notifications + + + {unreadNotificationCount} new + +
+ + + + setIsOpen(false)} + className="block rounded-lg border border-primary p-2 text-center text-sm font-medium tracking-wide text-primary outline-none transition-colors hover:bg-blue-light-5 focus:bg-blue-light-5 focus:text-primary focus-visible:border-primary dark:border-dark-3 dark:text-dark-6 dark:hover:border-dark-5 dark:hover:bg-dark-3 dark:hover:text-dark-7 dark:focus-visible:border-dark-5 dark:focus-visible:bg-dark-3 dark:focus-visible:text-dark-7" + > + See all notifications + +
+ + + { + setIsModalOpen(false); + }} + confirmText="Mark as read" + onConfirm={() => { + mutate(); + }} + > +
+

{currentNotification?.title}

+

{currentNotification?.message}

+
+
+ ); } diff --git a/src/components/Layouts/header/notification/notification-list.tsx b/src/components/Layouts/header/notification/notification-list.tsx new file mode 100644 index 0000000..9f3fb8e --- /dev/null +++ b/src/components/Layouts/header/notification/notification-list.tsx @@ -0,0 +1,51 @@ +"use client"; + +import Loading from "@/app/loading"; +import IsVisible from "@/components/ui/IsVisible"; +import { useGetMyNotifications } from "@/hooks/queries/useNotificationQueries"; +import { TNotificationDetailsResponse } from "@/lib/api/types/NotificationHistory"; +import { truncateString } from "@/utils/shared"; +import { Dispatch, SetStateAction, useEffect } from "react"; + +interface IProps { + setUnreadNotificationCount: Dispatch>; + onNotificationClick: (notification: TNotificationDetailsResponse) => void; +} + +const NotificationList = ({ + setUnreadNotificationCount, + onNotificationClick, +}: IProps) => { + const { data, isLoading } = useGetMyNotifications({ seen: false }); + useEffect(() => { + setUnreadNotificationCount(data?.data.length ?? 0); + }, [data, isLoading]); + + if (isLoading) return ; + + return ( +
    + +

    No new notification

    +
    + {data?.data.map((notification) => ( +
  • onNotificationClick(notification)} + > +
    + + {notification.title} + + + {truncateString(notification.message, 50, 50)} + +
    +
  • + ))} +
+ ); +}; + +export default NotificationList; diff --git a/src/components/forms/notifications/CreateNotification.tsx b/src/components/forms/notifications/CreateNotification.tsx index fa2c8fe..37c9ff2 100644 --- a/src/components/forms/notifications/CreateNotification.tsx +++ b/src/components/forms/notifications/CreateNotification.tsx @@ -51,6 +51,7 @@ const CreateNotificationForm = () => { name="schedule_at" label="schedule at" range={false} + timePicker placeholder="Select schedule date" /> @@ -100,7 +101,7 @@ const CreateNotificationForm = () => { {...register("link", { pattern: { value: REGEX.URL, - message: FormErrorMessages.invalidPattern, + message: FormErrorMessages.invalidPattern, }, })} name="link" @@ -152,7 +153,13 @@ const CreateNotificationForm = () => {
void; onConfirm?: () => void; children: ReactNode; + confirmText?: string; + cancelText?: string; } const Modal: React.FC = ({ @@ -15,6 +17,8 @@ const Modal: React.FC = ({ onClose, children, onConfirm, + confirmText = "confirm", + cancelText = "close", }) => { const [mounted, setMounted] = useState(false); const isMutating = useIsMutating(); @@ -55,21 +59,21 @@ const Modal: React.FC = ({
diff --git a/src/hooks/queries/useNotificationQueries.ts b/src/hooks/queries/useNotificationQueries.ts index 23152ac..c0c0896 100644 --- a/src/hooks/queries/useNotificationQueries.ts +++ b/src/hooks/queries/useNotificationQueries.ts @@ -16,6 +16,38 @@ export const useGetNotificationHistoryQuery = ( queryFn: () => notificationService.getNotifications(params), }); }; +export const useGetMyNotifications = (params?: Record) => { + const queryKey = useMemo( + () => [API_ENDPOINTS.NOTIFICATIONS.MY_NOTIFICATIONS, params], + [params], + ); + return useQuery({ + queryKey, + queryFn: () => notificationService.getMyNotifications(params), + }); +}; +export const useMarkNotificationAsSeen = ( + id: string, + successCallback?: () => void, +) => { + const queryClient = useQueryClient(); + const mutationKey = useMemo( + () => [API_ENDPOINTS.NOTIFICATIONS.MARK_AS_SEEN(id), id], + [id], + ); + return useMutation({ + mutationKey, + mutationFn: () => notificationService.markNotificationAsSeen(id), + onSuccess: () => { + if (successCallback) { + successCallback(); + } + queryClient.invalidateQueries({ + queryKey: [API_ENDPOINTS.NOTIFICATIONS.MY_NOTIFICATIONS], + }); + }, + }); +}; export const useGetNotificationDetails = (id: string) => { const queryKey = useMemo( () => [API_ENDPOINTS.NOTIFICATIONS.DETAILS(id), "NOTIFICATION DETAILS"], @@ -40,6 +72,9 @@ export const useCreateNotificationQuery = (successCallback?: () => void) => { queryClient.invalidateQueries({ queryKey: [API_ENDPOINTS.NOTIFICATIONS.HISTORY], }); + queryClient.invalidateQueries({ + queryKey: [API_ENDPOINTS.NOTIFICATIONS.MY_NOTIFICATIONS], + }); if (successCallback) { successCallback(); } diff --git a/src/lib/api/endpoints.ts b/src/lib/api/endpoints.ts index 8a1c352..9d38536 100644 --- a/src/lib/api/endpoints.ts +++ b/src/lib/api/endpoints.ts @@ -54,5 +54,7 @@ export const API_ENDPOINTS = { HISTORY: "notifications", CREATE: "notifications", DETAILS: (id: string) => `notifications/view/${id}`, + MY_NOTIFICATIONS: "notifications/my", + MARK_AS_SEEN: (id: string) => `notifications/mark-seen/${id}`, }, } as const; diff --git a/src/lib/api/services/notification-service.ts b/src/lib/api/services/notification-service.ts index 33518fd..ab452e5 100644 --- a/src/lib/api/services/notification-service.ts +++ b/src/lib/api/services/notification-service.ts @@ -8,6 +8,32 @@ import { import { ApiResponse } from "../types/shared"; export const notificationService = { + getMyNotifications: async ( + params?: Record, + ): Promise => { + try { + return ( + await api.get(API_ENDPOINTS.NOTIFICATIONS.MY_NOTIFICATIONS, { params }) + ).data; + } catch (error) { + console.error( + "ERROR Caught in notification service => getMyNotifications", + error, + ); + throw error; + } + }, + markNotificationAsSeen: async (id: string) => { + try { + return (await api.get(API_ENDPOINTS.NOTIFICATIONS.MARK_AS_SEEN(id))).data; + } catch (error) { + console.error( + "ERROR Caught in notification service => markNotificationAsRead", + error, + ); + throw error; + } + }, getNotifications: async ( params?: Record, ): Promise => { diff --git a/src/utils/shared.ts b/src/utils/shared.ts index b451b25..5b13ddf 100644 --- a/src/utils/shared.ts +++ b/src/utils/shared.ts @@ -72,3 +72,13 @@ export const getEnumAsArray = (target: T): ILabelValue[] => { value, })); }; +export const truncateString = ( + str: string, + startLength: number = 10, + endLength: number = 10, +): string => { + if (str.length <= startLength + endLength) { + return str; + } + return str.slice(0, startLength) + "..." + str.slice(-endLength); +};