Quran_Tech_Server / F_Pro /src /components /sessions /SheikhInterviewSession.tsx
aboalaa147's picture
Initial deployment
eb6a2f9
Raw
History Blame Contribute Delete
10.3 kB
import { useState, useEffect, useRef } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Button } from '../ui/button';
import { Badge } from '../ui/badge';
import { Textarea } from '../ui/textarea';
import {
Video, VideoOff, Mic, MicOff, PhoneOff,
MessageSquare, Wifi, AlertTriangle, Clock,
} from 'lucide-react';
import { useAuth } from '../../lib/authContext';
import { useWebRTC } from '../../hooks/useWebRTC';
import { getApiBaseUrl } from '../../lib/api/config';
const BASE_URL = getApiBaseUrl();
/**
* The sheikh's side of the verification interview.
* Joins the same WebRTC room as the admin (room = "interview-{sheikhId}").
* No approve/reject logic — just the video call.
*/
export function SheikhInterviewSession() {
const { sheikhId } = useParams<{ sheikhId: string }>();
const navigate = useNavigate();
const { user } = useAuth();
const roomId = `interview-${sheikhId ?? 'unknown'}`;
const [isVideoOn, setIsVideoOn] = useState(true);
const [isAudioOn, setIsAudioOn] = useState(true);
const [sessionTime, setSessionTime] = useState(0);
const [newMessage, setNewMessage] = useState('');
const [showChat, setShowChat] = useState(false);
const localVideoRef = useRef<HTMLVideoElement>(null);
const remoteVideoRef = useRef<HTMLVideoElement>(null);
const {
localStream, remoteStream, isConnected,
messages, sendChatMessage,
toggleVideo, toggleAudio, connectionQuality,
} = useWebRTC(roomId, user?.id ?? 'sheikh');
useEffect(() => {
if (localVideoRef.current && localStream) localVideoRef.current.srcObject = localStream;
}, [localStream]);
useEffect(() => {
if (remoteVideoRef.current && remoteStream) {
remoteVideoRef.current.srcObject = remoteStream;
}
}, [remoteStream]);
useEffect(() => {
const timer = setInterval(() => setSessionTime(prev => prev + 1), 1000);
return () => clearInterval(timer);
}, []);
// Poll every 10s for admin's decision — redirect when approved or rejected
useEffect(() => {
const token = localStorage.getItem('authToken') ?? '';
if (!token) return;
const checkDecision = async () => {
try {
const res = await fetch(`${BASE_URL}/api/auth/verify-profile`, {
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
});
if (!res.ok) return;
const data = await res.json();
const status: string = data.sheikhApprovalStatus ?? '';
if (status === 'APPROVED') {
navigate('/sheikh/dashboard', { replace: true });
} else if (status === 'REJECTED') {
const reason: string = data.rejectionReason ?? '';
navigate('/sheikh/interview', {
replace: true,
state: { rejected: true, reason },
});
}
} catch {
// silent — background poll, don't disrupt UI
}
};
const timer = setInterval(checkDecision, 10_000);
return () => clearInterval(timer);
}, [navigate]);
const formatTime = (seconds: number) => {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
};
const handleSendMessage = () => {
if (newMessage.trim()) { sendChatMessage(newMessage); setNewMessage(''); }
};
const handleLeave = () => {
navigate('/sheikh/interview');
};
const getConnectionColor = () => {
switch (connectionQuality) {
case 'good': return 'text-emerald-500';
case 'fair': return 'text-yellow-500';
case 'poor': return 'text-red-500';
default: return 'text-gray-500';
}
};
const getConnectionLabel = () => {
if (!isConnected) return 'Connecting...';
switch (connectionQuality) {
case 'good': return 'Excellent';
case 'fair': return 'Stable';
case 'poor': return 'Poor Connection';
default: return 'Waiting for Admin...';
}
};
return (
<div className="h-screen bg-gray-900 flex flex-col">
{/* Header */}
<div className="bg-gray-800 border-b border-gray-700 px-6 py-3">
<div className="flex items-center justify-between">
<div>
<h2 className="text-white font-semibold">Verification Interview</h2>
<p className="text-gray-400 text-sm">With the review team</p>
</div>
<div className="flex items-center gap-6">
<div className={`flex items-center gap-1.5 px-3 py-1 rounded-full bg-gray-700/50 border border-gray-600 ${getConnectionColor()}`}>
{connectionQuality === 'poor'
? <AlertTriangle className="h-3.5 w-3.5" />
: <Wifi className="h-3.5 w-3.5" />}
<span className="text-xs font-medium">{getConnectionLabel()}</span>
</div>
<div className="flex items-center gap-2 text-white border-l border-gray-700 pl-6">
<Clock className="h-4 w-4 text-emerald-500" />
<span className="font-mono text-sm">{formatTime(sessionTime)}</span>
</div>
<Badge variant="destructive" className="animate-pulse px-3">LIVE</Badge>
</div>
</div>
</div>
{/* Main Content */}
<div className="flex-1 flex overflow-hidden">
{/* Video Area */}
<div className="flex-1 relative bg-black">
<div className="absolute inset-0 flex items-center justify-center">
{/* Remote video always in DOM so ref is always attached */}
<video
ref={remoteVideoRef}
autoPlay
playsInline
className={`w-full h-full object-cover ${remoteStream ? '' : 'hidden'}`}
/>
{!remoteStream && (
<div className="w-full h-full bg-gradient-to-br from-gray-800 to-gray-900 flex items-center justify-center">
<div className="text-center">
<div className="w-32 h-32 rounded-full bg-emerald-600 flex items-center justify-center text-white text-4xl mx-auto mb-4">
A
</div>
<p className="text-white text-xl">Admin</p>
<p className="text-gray-400 mt-2">Waiting for admin to join...</p>
</div>
</div>
)}
</div>
{/* Local PiP */}
<div className="absolute top-4 right-4 w-48 h-36 bg-black rounded-lg shadow-lg border-2 border-white overflow-hidden">
<video
ref={localVideoRef}
autoPlay playsInline muted
className={`w-full h-full object-cover ${!isVideoOn ? 'hidden' : ''}`}
/>
{!isVideoOn && (
<div className="w-full h-full flex items-center justify-center bg-gray-800">
<div className="text-center">
<div className="w-12 h-12 rounded-full bg-blue-600 flex items-center justify-center text-white mx-auto mb-1">
{user?.name?.[0] ?? 'S'}
</div>
<p className="text-white text-xs">You (Video Off)</p>
</div>
</div>
)}
</div>
</div>
{/* Chat Sidebar */}
{showChat && (
<div className="w-80 bg-gray-800 border-l border-gray-700 flex flex-col">
<div className="p-4 border-b border-gray-700">
<h3 className="text-white font-medium">Chat</h3>
</div>
<div className="flex-1 overflow-y-auto p-4 space-y-3">
{messages.map((msg, i) => (
<div key={i} className={`flex flex-col ${msg.sender === 'You' ? 'items-end' : 'items-start'}`}>
<div className={`max-w-[80%] rounded-lg p-3 ${msg.sender === 'You' ? 'bg-emerald-600' : 'bg-gray-700'}`}>
<p className="text-white text-sm">{msg.message}</p>
</div>
<p className="text-gray-400 text-xs mt-1">{msg.sender} · {msg.time}</p>
</div>
))}
</div>
<div className="p-4 border-t border-gray-700 flex gap-2">
<Textarea
value={newMessage}
onChange={e => setNewMessage(e.target.value)}
onKeyDown={e => e.key === 'Enter' && !e.shiftKey && (e.preventDefault(), handleSendMessage())}
placeholder="Type a message..."
rows={2}
className="bg-gray-700 border-gray-600 text-white"
/>
<Button onClick={handleSendMessage} size="sm">Send</Button>
</div>
</div>
)}
</div>
{/* Controls */}
<div className="bg-gray-800 border-t border-gray-700 px-6 py-4">
<div className="flex items-center justify-center gap-4">
<Button
variant={isVideoOn ? 'outline' : 'destructive'}
size="lg"
className={isVideoOn ? 'bg-gray-700 border-gray-600 hover:bg-gray-600 text-white' : ''}
onClick={() => { setIsVideoOn(!isVideoOn); toggleVideo(); }}
>
{isVideoOn ? <Video className="h-5 w-5" /> : <VideoOff className="h-5 w-5" />}
</Button>
<Button
variant={isAudioOn ? 'outline' : 'destructive'}
size="lg"
className={isAudioOn ? 'bg-gray-700 border-gray-600 hover:bg-gray-600 text-white' : ''}
onClick={() => { setIsAudioOn(!isAudioOn); toggleAudio(); }}
>
{isAudioOn ? <Mic className="h-5 w-5" /> : <MicOff className="h-5 w-5" />}
</Button>
<Button
variant={showChat ? 'default' : 'outline'}
size="lg"
className={showChat ? 'bg-emerald-600 text-white hover:bg-emerald-700' : 'bg-gray-700 border-gray-600 hover:bg-gray-600 text-white'}
onClick={() => setShowChat(!showChat)}
>
<MessageSquare className="h-5 w-5" />
</Button>
<Button variant="destructive" size="lg" onClick={handleLeave}>
<PhoneOff className="h-5 w-5 mr-2" />
Leave Interview
</Button>
</div>
</div>
</div>
);
}