refactor Notifications rc
This commit is contained in:
parent
1dae207b6e
commit
b1787aea1b
130
components/incoming-call-notification.tsx
Normal file
130
components/incoming-call-notification.tsx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Phone, PhoneOff, X } from 'lucide-react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
export interface IncomingCall {
|
||||||
|
from: {
|
||||||
|
userId: string;
|
||||||
|
username: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
roomId: string;
|
||||||
|
roomName: string;
|
||||||
|
timestamp: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IncomingCallNotificationProps {
|
||||||
|
call: IncomingCall | null;
|
||||||
|
onDismiss: () => void;
|
||||||
|
onAccept: (roomId: string) => void;
|
||||||
|
onReject: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IncomingCallNotification({
|
||||||
|
call,
|
||||||
|
onDismiss,
|
||||||
|
onAccept,
|
||||||
|
onReject,
|
||||||
|
}: IncomingCallNotificationProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (call) {
|
||||||
|
setIsVisible(true);
|
||||||
|
|
||||||
|
// Auto-dismiss after 30 seconds if user doesn't interact
|
||||||
|
const autoDismissTimer = setTimeout(() => {
|
||||||
|
setIsVisible(false);
|
||||||
|
onDismiss();
|
||||||
|
}, 30000); // 30 seconds
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(autoDismissTimer);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
setIsVisible(false);
|
||||||
|
}
|
||||||
|
}, [call, onDismiss]);
|
||||||
|
|
||||||
|
if (!call || !isVisible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAccept = () => {
|
||||||
|
onAccept(call.roomId);
|
||||||
|
// Navigate to parole page with room ID
|
||||||
|
router.push(`/parole?room=${call.roomId}`);
|
||||||
|
setIsVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReject = () => {
|
||||||
|
onReject();
|
||||||
|
setIsVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDismiss = () => {
|
||||||
|
onDismiss();
|
||||||
|
setIsVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const callerName = call.from.name || call.from.username || 'Inconnu';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed top-4 right-4 z-[9999] animate-in slide-in-from-top-5 duration-300">
|
||||||
|
<div className="bg-white rounded-lg shadow-2xl border-2 border-blue-500 p-5 min-w-[340px] max-w-[420px]">
|
||||||
|
{/* Header with Outlook-like style */}
|
||||||
|
<div className="flex items-start justify-between mb-4 pb-3 border-b border-gray-200">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center flex-shrink-0">
|
||||||
|
<Phone className="w-5 h-5 text-blue-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-base text-gray-900 leading-tight">Parole</h3>
|
||||||
|
<p className="text-xs text-gray-500 mt-0.5">Appel entrant</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleDismiss}
|
||||||
|
className="text-gray-400 hover:text-gray-600 transition-colors p-1 -mt-1 -mr-1"
|
||||||
|
aria-label="Fermer"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Call Info - Outlook style message */}
|
||||||
|
<div className="mb-5">
|
||||||
|
<p className="text-gray-800 text-sm leading-relaxed">
|
||||||
|
Vous avez un appel de <span className="font-semibold text-gray-900">{callerName}</span>
|
||||||
|
</p>
|
||||||
|
{call.roomName && call.roomName !== callerName && (
|
||||||
|
<p className="text-xs text-gray-500 mt-1.5">Dans {call.roomName}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Actions - Outlook style buttons */}
|
||||||
|
<div className="flex gap-2.5">
|
||||||
|
<Button
|
||||||
|
onClick={handleAccept}
|
||||||
|
className="flex-1 bg-green-600 hover:bg-green-700 text-white font-medium py-2.5 text-sm shadow-sm"
|
||||||
|
>
|
||||||
|
<Phone className="w-4 h-4 mr-2" />
|
||||||
|
Accepter
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleReject}
|
||||||
|
variant="destructive"
|
||||||
|
className="flex-1 font-medium py-2.5 text-sm shadow-sm"
|
||||||
|
>
|
||||||
|
<PhoneOff className="w-4 h-4 mr-2" />
|
||||||
|
Raccrocher
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@ import { Toaster } from "@/components/ui/toaster";
|
|||||||
import { useBackgroundImage } from "@/components/background-switcher";
|
import { useBackgroundImage } from "@/components/background-switcher";
|
||||||
import { clearAuthCookies, clearKeycloakCookies } from "@/lib/session";
|
import { clearAuthCookies, clearKeycloakCookies } from "@/lib/session";
|
||||||
import { useRocketChatCalls } from "@/hooks/use-rocketchat-calls";
|
import { useRocketChatCalls } from "@/hooks/use-rocketchat-calls";
|
||||||
|
import { IncomingCallNotification } from "@/components/incoming-call-notification";
|
||||||
|
|
||||||
interface LayoutWrapperProps {
|
interface LayoutWrapperProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@ -206,6 +207,23 @@ export function LayoutWrapper({ children, isSignInPage, isAuthenticated }: Layou
|
|||||||
</div>
|
</div>
|
||||||
{!isSignInPage && isAuthenticated && <Footer />}
|
{!isSignInPage && isAuthenticated && <Footer />}
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
|
||||||
|
{/* Incoming call notification */}
|
||||||
|
{!isSignInPage && isAuthenticated && (
|
||||||
|
<IncomingCallNotification
|
||||||
|
call={incomingCall}
|
||||||
|
onDismiss={() => setIncomingCall(null)}
|
||||||
|
onAccept={(roomId) => {
|
||||||
|
console.log('[LayoutWrapper] Call accepted, navigating to room:', roomId);
|
||||||
|
setIncomingCall(null);
|
||||||
|
}}
|
||||||
|
onReject={() => {
|
||||||
|
console.log('[LayoutWrapper] Call rejected');
|
||||||
|
setIncomingCall(null);
|
||||||
|
// TODO: Send reject signal to RocketChat if needed
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</AuthCheck>
|
</AuthCheck>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1,8 +1,9 @@
|
|||||||
import { useEffect, useRef, useCallback } from 'react';
|
import { useEffect, useRef, useCallback, useState } from 'react';
|
||||||
import { useSession } from 'next-auth/react';
|
import { useSession } from 'next-auth/react';
|
||||||
import { RocketChatCallListener, CallEvent } from '@/lib/services/rocketchat-call-listener';
|
import { RocketChatCallListener, CallEvent } from '@/lib/services/rocketchat-call-listener';
|
||||||
import { useWidgetNotification } from './use-widget-notification';
|
import { useWidgetNotification } from './use-widget-notification';
|
||||||
import { logger } from '@/lib/logger';
|
import { logger } from '@/lib/logger';
|
||||||
|
import { IncomingCall } from '@/components/incoming-call-notification';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook to listen for incoming RocketChat calls and trigger notifications
|
* Hook to listen for incoming RocketChat calls and trigger notifications
|
||||||
@ -13,6 +14,7 @@ export function useRocketChatCalls() {
|
|||||||
const listenerRef = useRef<RocketChatCallListener | null>(null);
|
const listenerRef = useRef<RocketChatCallListener | null>(null);
|
||||||
const unsubscribeRef = useRef<(() => void) | null>(null);
|
const unsubscribeRef = useRef<(() => void) | null>(null);
|
||||||
const initializedRef = useRef(false);
|
const initializedRef = useRef(false);
|
||||||
|
const [incomingCall, setIncomingCall] = useState<IncomingCall | null>(null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get RocketChat credentials for the current user
|
* Get RocketChat credentials for the current user
|
||||||
@ -176,4 +178,9 @@ export function useRocketChatCalls() {
|
|||||||
initializedRef.current = false;
|
initializedRef.current = false;
|
||||||
};
|
};
|
||||||
}, [session?.user?.id, initializeListener]);
|
}, [session?.user?.id, initializeListener]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
incomingCall,
|
||||||
|
setIncomingCall,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -382,10 +382,14 @@ export class RocketChatCallListener {
|
|||||||
const notification = args[0];
|
const notification = args[0];
|
||||||
const payload = notification.payload;
|
const payload = notification.payload;
|
||||||
|
|
||||||
// Check if it's a videoconf (video call) message
|
// Only process call-related notifications (videoconf, audio, video)
|
||||||
if (payload?.message?.t === 'videoconf' || payload?.message?.t === 'audio' || payload?.message?.t === 'video') {
|
// Ignore system notifications like version updates, etc.
|
||||||
|
const messageType = payload?.message?.t;
|
||||||
|
const isCallNotification = messageType === 'videoconf' || messageType === 'audio' || messageType === 'video';
|
||||||
|
|
||||||
|
if (isCallNotification) {
|
||||||
console.log('[ROCKETCHAT_CALL_LISTENER] ✅ VIDEO/AUDIO CALL DETECTED in notification!', {
|
console.log('[ROCKETCHAT_CALL_LISTENER] ✅ VIDEO/AUDIO CALL DETECTED in notification!', {
|
||||||
type: payload.message.t,
|
type: messageType,
|
||||||
sender: payload.sender,
|
sender: payload.sender,
|
||||||
roomId: payload.rid,
|
roomId: payload.rid,
|
||||||
});
|
});
|
||||||
@ -399,22 +403,31 @@ export class RocketChatCallListener {
|
|||||||
roomName: payload.name || notification.title,
|
roomName: payload.name || notification.title,
|
||||||
message: payload.message,
|
message: payload.message,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// Log ignored notifications for debugging (but don't process them)
|
||||||
|
console.log('[ROCKETCHAT_CALL_LISTENER] ⏭️ Ignoring non-call notification', {
|
||||||
|
messageType,
|
||||||
|
notificationId: payload?._id || notification?.id,
|
||||||
|
title: notification?.title || payload?.title,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is a webrtc event
|
// Check if this is a webrtc event (only process if it looks like an incoming call)
|
||||||
if (eventName?.includes('/webrtc')) {
|
if (eventName?.includes('/webrtc')) {
|
||||||
console.log('[ROCKETCHAT_CALL_LISTENER] ✅ This is a webrtc event!');
|
console.log('[ROCKETCHAT_CALL_LISTENER] ✅ This is a webrtc event!');
|
||||||
if (args.length > 0) {
|
if (args.length > 0) {
|
||||||
this.handleCallEvent(args[0]);
|
const webrtcData = args[0];
|
||||||
} else {
|
// Only process if it's an incoming call (ringing, offer, etc.)
|
||||||
// Sometimes the event data might be in the message itself
|
if (webrtcData.action === 'ringing' || webrtcData.type === 'call' || webrtcData.event === 'incoming') {
|
||||||
this.handleCallEvent(message.fields);
|
this.handleCallEvent(webrtcData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also check for other possible call-related events
|
// Also check for other possible call-related events (but be more specific)
|
||||||
if (eventName?.includes('/call') || eventName?.includes('/video') || eventName?.includes('/audio')) {
|
// Only process if the event name explicitly indicates a call
|
||||||
|
if (eventName?.includes('/call') && (eventName?.includes('/incoming') || eventName?.includes('/ringing'))) {
|
||||||
console.log('[ROCKETCHAT_CALL_LISTENER] ✅ This might be a call event!');
|
console.log('[ROCKETCHAT_CALL_LISTENER] ✅ This might be a call event!');
|
||||||
if (args.length > 0) {
|
if (args.length > 0) {
|
||||||
this.handleCallEvent(args[0]);
|
this.handleCallEvent(args[0]);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user