diff --git a/public/mixkit-correct-answer-tone-2870.wav b/public/mixkit-correct-answer-tone-2870.wav new file mode 100644 index 0000000..88a18e1 Binary files /dev/null and b/public/mixkit-correct-answer-tone-2870.wav differ diff --git a/src/components/Notification.tsx b/src/components/Notification.tsx index 011a0ca..6b478ab 100644 --- a/src/components/Notification.tsx +++ b/src/components/Notification.tsx @@ -1,36 +1,78 @@ import { getToken } from '@/config/func' import { useSocket } from '@/pages/pager/hooks/useSocket' -import { type FC, useEffect, useCallback } from 'react' +import { type FC, useEffect, useCallback, useState } from 'react' import type { NotificationPayload } from '@/types/notification.types' +import NotificationItem from '@/components/NotificationItem' + +interface NotificationItemData extends NotificationPayload { + id: string + timestamp: number +} const Notification: FC = () => { + const [notifications, setNotifications] = useState([]) + const [audioEnabled, setAudioEnabled] = useState(false) const token = getToken() const socketUrl = `${import.meta.env.VITE_SOCKET_URL}/notifications` const { connect, socket, error } = useSocket(socketUrl, token!) - // لاگ کردن خطاها useEffect(() => { if (error) { console.error('❌ Notification: خطا در اتصال:', error) } }, [error]) - useEffect(() => { connect() }, [connect]) - const handleGetNotification = useCallback((data: NotificationPayload) => { + useEffect(() => { + const enableAudio = async () => { + try { + const audio = new Audio('/mixkit-correct-answer-tone-2870.wav') + audio.volume = 0 + await audio.play() + audio.pause() + audio.currentTime = 0 + setAudioEnabled(true) + } catch { + console.warn('صدا هنوز فعال نشده است') + } + } - console.log('✅ Notification: دریافت نوتیفیکیشن‌ها:', data) - // alert('دریافت نوتیفیکیشن‌ها: ' + JSON.stringify(data)) + const handleUserInteraction = () => { + if (!audioEnabled) { + enableAudio() + } + } + + window.addEventListener('click', handleUserInteraction, { once: true }) + window.addEventListener('touchstart', handleUserInteraction, { once: true }) + window.addEventListener('keydown', handleUserInteraction, { once: true }) + + return () => { + window.removeEventListener('click', handleUserInteraction) + window.removeEventListener('touchstart', handleUserInteraction) + window.removeEventListener('keydown', handleUserInteraction) + } + }, [audioEnabled]) + + const handleGetNotification = useCallback((data: NotificationPayload) => { + const newNotification: NotificationItemData = { + ...data, + id: `${Date.now()}-${Math.random()}`, + timestamp: Date.now(), + } + setNotifications((prev) => [...prev, newNotification]) }, []) + const handleRemoveNotification = useCallback((id: string) => { + setNotifications((prev) => prev.filter((notif) => notif.id !== id)) + }, []) useEffect(() => { if (socket) { - socket.on('joined', () => null) socket.on('notifications', handleGetNotification) @@ -40,9 +82,24 @@ const Notification: FC = () => { } }, [socket, handleGetNotification]) + if (notifications.length === 0) { + return null + } - - return null + return ( +
+ {notifications.map((notification) => ( + + ))} +
+ ) } -export default Notification \ No newline at end of file +export default Notification diff --git a/src/components/NotificationItem.tsx b/src/components/NotificationItem.tsx new file mode 100644 index 0000000..7d07dad --- /dev/null +++ b/src/components/NotificationItem.tsx @@ -0,0 +1,97 @@ +import { type FC, useEffect } from 'react' +import { CloseCircle, NotificationBing, ShoppingBag, MessageText1 } from 'iconsax-react' +import Button from '@/components/Button' +import { NotificationSubject } from '@/types/notification.types' + +interface NotificationItemProps { + id: string + subject: string + body: string + onRemove: (id: string) => void + audioEnabled: boolean +} + +const NotificationItem: FC = ({ id, subject, body, onRemove, audioEnabled }) => { + useEffect(() => { + if (!audioEnabled) { + return + } + + const playNotificationSound = async () => { + try { + const audio = new Audio('/mixkit-correct-answer-tone-2870.wav') + audio.volume = 0.5 + + audio.addEventListener('error', (err) => { + console.error('❌ خطا در لود فایل صوتی:', err) + }, { once: true }) + + await audio.play() + } catch (err) { + console.warn('خطا در پخش صدا:', err) + } + } + + playNotificationSound() + }, [audioEnabled]) + const getNotificationConfig = (subjectType: string) => { + switch (subjectType) { + case NotificationSubject.PAGER_CREATED: + return { + icon: , + title: 'پیجر جدید', + } + case NotificationSubject.ORDER_CREATED: + return { + icon: , + title: 'سفارش جدید', + } + default: + return { + icon: , + title: 'اطلاعیه', + } + } + } + + const config = getNotificationConfig(subject) + + return ( +
+
+
+
+ {config.icon} +
+
+
+
+

+ {config.title} +

+ +
+

+ {body || 'نوتیفیکیشن جدید'} +

+
+ +
+
+
+
+ ) +} + +export default NotificationItem diff --git a/src/types/notification.types.ts b/src/types/notification.types.ts index 377b385..ed6e94a 100644 --- a/src/types/notification.types.ts +++ b/src/types/notification.types.ts @@ -2,3 +2,8 @@ export interface NotificationPayload { subject: string; body: string; } + +export const enum NotificationSubject { + PAGER_CREATED = "pager.created", + ORDER_CREATED = "order.created", +}