2026-01-21 18:13:35 +01:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import { useEffect, useRef } from 'react';
|
|
|
|
|
import useSWR from 'swr';
|
|
|
|
|
|
|
|
|
|
interface User {
|
|
|
|
|
id: string;
|
|
|
|
|
email: string;
|
|
|
|
|
name: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface LastMessage {
|
|
|
|
|
id: string;
|
|
|
|
|
content: string | null;
|
|
|
|
|
senderId: string;
|
|
|
|
|
senderName: string;
|
|
|
|
|
createdAt: string;
|
|
|
|
|
hasFiles: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface Conversation {
|
|
|
|
|
id: string;
|
|
|
|
|
name: string | null;
|
|
|
|
|
type: string;
|
|
|
|
|
displayName: string;
|
|
|
|
|
participants: Array<{ id: string; email: string; name: string | null }>;
|
|
|
|
|
lastMessage: LastMessage | null;
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
createdAt: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fetcher = (url: string) => fetch(url).then((res) => res.json());
|
|
|
|
|
|
|
|
|
|
export default function MessageNotifications() {
|
|
|
|
|
const { data: currentUser } = useSWR<User>('/api/auth/me', fetcher);
|
|
|
|
|
const { data: conversations } = useSWR<Conversation[]>(
|
|
|
|
|
currentUser ? '/api/conversations' : null,
|
|
|
|
|
fetcher,
|
|
|
|
|
{
|
|
|
|
|
refreshInterval: 3000, // Vérifier toutes les 3 secondes
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const lastMessageIdsRef = useRef<Map<string, string>>(new Map());
|
|
|
|
|
const initializedRef = useRef(false);
|
|
|
|
|
|
|
|
|
|
// Fonction pour jouer le son de notification
|
|
|
|
|
const playNotificationSound = () => {
|
|
|
|
|
try {
|
|
|
|
|
const AudioContext = window.AudioContext || (window as any).webkitAudioContext;
|
|
|
|
|
const audioContext = new AudioContext();
|
|
|
|
|
const oscillator = audioContext.createOscillator();
|
|
|
|
|
const gainNode = audioContext.createGain();
|
|
|
|
|
|
|
|
|
|
oscillator.connect(gainNode);
|
|
|
|
|
gainNode.connect(audioContext.destination);
|
|
|
|
|
|
|
|
|
|
// Son de notification agréable (deux tons)
|
|
|
|
|
oscillator.frequency.value = 800;
|
|
|
|
|
oscillator.type = 'sine';
|
|
|
|
|
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
|
|
|
|
|
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2);
|
|
|
|
|
|
|
|
|
|
oscillator.start(audioContext.currentTime);
|
|
|
|
|
oscillator.stop(audioContext.currentTime + 0.2);
|
|
|
|
|
|
|
|
|
|
// Deuxième ton après une courte pause
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
const oscillator2 = audioContext.createOscillator();
|
|
|
|
|
const gainNode2 = audioContext.createGain();
|
|
|
|
|
|
|
|
|
|
oscillator2.connect(gainNode2);
|
|
|
|
|
gainNode2.connect(audioContext.destination);
|
|
|
|
|
|
|
|
|
|
oscillator2.frequency.value = 1000;
|
|
|
|
|
oscillator2.type = 'sine';
|
|
|
|
|
gainNode2.gain.setValueAtTime(0.3, audioContext.currentTime);
|
|
|
|
|
gainNode2.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2);
|
|
|
|
|
|
|
|
|
|
oscillator2.start(audioContext.currentTime);
|
|
|
|
|
oscillator2.stop(audioContext.currentTime + 0.2);
|
|
|
|
|
}, 150);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Erreur lors de la lecture du son:', error);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Demander la permission pour les notifications
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if ('Notification' in window && Notification.permission === 'default') {
|
|
|
|
|
Notification.requestPermission().catch(console.error);
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// Initialiser les IDs des messages au premier chargement
|
|
|
|
|
useEffect(() => {
|
2026-01-22 16:49:51 +01:00
|
|
|
if (conversations && Array.isArray(conversations) && !initializedRef.current) {
|
2026-01-21 18:13:35 +01:00
|
|
|
conversations.forEach((conversation) => {
|
|
|
|
|
if (conversation.lastMessage) {
|
|
|
|
|
lastMessageIdsRef.current.set(conversation.id, conversation.lastMessage.id);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
initializedRef.current = true;
|
|
|
|
|
}
|
|
|
|
|
}, [conversations]);
|
|
|
|
|
|
|
|
|
|
// Détecter les nouveaux messages
|
|
|
|
|
useEffect(() => {
|
2026-01-22 16:49:51 +01:00
|
|
|
if (!conversations || !Array.isArray(conversations) || !currentUser || !initializedRef.current) return;
|
2026-01-21 18:13:35 +01:00
|
|
|
|
|
|
|
|
conversations.forEach((conversation) => {
|
|
|
|
|
if (!conversation.lastMessage) return;
|
|
|
|
|
|
|
|
|
|
const lastMessageId = lastMessageIdsRef.current.get(conversation.id);
|
|
|
|
|
const currentMessageId = conversation.lastMessage.id;
|
|
|
|
|
|
|
|
|
|
// Nouveau message détecté
|
|
|
|
|
if (lastMessageId && lastMessageId !== currentMessageId) {
|
|
|
|
|
// Vérifier si le message n'est pas de l'utilisateur actuel
|
|
|
|
|
if (conversation.lastMessage.senderId !== currentUser.id) {
|
|
|
|
|
// Vérifier si on est sur la page de messagerie
|
|
|
|
|
const isOnMessagingPage = window.location.pathname === '/dashboard/messagerie';
|
|
|
|
|
|
|
|
|
|
// Jouer le son seulement si on n'est pas sur la page de messagerie
|
|
|
|
|
// (sur la page de messagerie, on voit déjà les messages en temps réel)
|
|
|
|
|
if (!isOnMessagingPage) {
|
|
|
|
|
playNotificationSound();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Afficher la notification si on n'est pas sur la page de messagerie
|
|
|
|
|
if (!isOnMessagingPage && 'Notification' in window && Notification.permission === 'granted') {
|
|
|
|
|
const messageContent = conversation.lastMessage.hasFiles && !conversation.lastMessage.content
|
|
|
|
|
? '📎 Fichier'
|
|
|
|
|
: conversation.lastMessage.content || '📎 Fichier';
|
|
|
|
|
|
|
|
|
|
const notification = new Notification(
|
|
|
|
|
conversation.type === 'group' ? conversation.displayName : conversation.lastMessage.senderName,
|
|
|
|
|
{
|
|
|
|
|
body: messageContent.length > 100
|
|
|
|
|
? messageContent.substring(0, 100) + '...'
|
|
|
|
|
: messageContent,
|
|
|
|
|
icon: '/logo.svg',
|
|
|
|
|
badge: '/logo.svg',
|
|
|
|
|
tag: conversation.id, // Permet de remplacer les notifications de la même conversation
|
|
|
|
|
requireInteraction: false,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
notification.onclick = () => {
|
|
|
|
|
window.focus();
|
|
|
|
|
window.location.href = '/dashboard/messagerie';
|
|
|
|
|
notification.close();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Fermer automatiquement après 5 secondes
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
notification.close();
|
|
|
|
|
}, 5000);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mettre à jour l'ID du dernier message
|
|
|
|
|
lastMessageIdsRef.current.set(conversation.id, currentMessageId);
|
|
|
|
|
});
|
|
|
|
|
}, [conversations, currentUser]);
|
|
|
|
|
|
|
|
|
|
return null; // Ce composant ne rend rien visuellement
|
|
|
|
|
}
|