227 lines
10 KiB
TypeScript
227 lines
10 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect, use, useCallback } from "react"
|
|
import { Phone, PhoneOff, Mic, MicOff, Volume2, VolumeX, Users, Loader2 } from "lucide-react"
|
|
import { useWebRTCCall } from "@/hooks/useWebRTCCall"
|
|
|
|
export default function CallRoomPage({ params }: { params: Promise<{ roomId: string }> }) {
|
|
const { roomId } = use(params)
|
|
|
|
const {
|
|
callState,
|
|
isMuted,
|
|
isSpeakerOn,
|
|
callDuration,
|
|
error,
|
|
isReady,
|
|
participants,
|
|
joinRoom,
|
|
endCall,
|
|
toggleMute,
|
|
toggleSpeaker,
|
|
formatDuration,
|
|
} = useWebRTCCall({ anonymous: true })
|
|
|
|
const [displayName, setDisplayName] = useState("")
|
|
const [joined, setJoined] = useState(false)
|
|
const [micError, setMicError] = useState<string | null>(null)
|
|
const [connectionTimeout, setConnectionTimeout] = useState(false)
|
|
|
|
useEffect(() => {
|
|
if (isReady) return
|
|
const timer = setTimeout(() => {
|
|
if (!isReady) setConnectionTimeout(true)
|
|
}, 10000)
|
|
return () => clearTimeout(timer)
|
|
}, [isReady])
|
|
|
|
const handleJoin = useCallback(async () => {
|
|
if (!displayName.trim()) return
|
|
setMicError(null)
|
|
try {
|
|
await navigator.mediaDevices.getUserMedia({ audio: true })
|
|
joinRoom(roomId, displayName.trim() || "Anonymous")
|
|
setJoined(true)
|
|
} catch {
|
|
setMicError("Microphone access is required to join the call. Please allow microphone access in your browser settings.")
|
|
}
|
|
}, [displayName, roomId, joinRoom])
|
|
|
|
const handleEnd = useCallback(() => {
|
|
endCall()
|
|
}, [endCall])
|
|
|
|
if (!joined) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-[#C84B4B]/10 to-[#C84B4B]/10 p-4">
|
|
<div className="bg-white dark:bg-[#141414] rounded-2xl p-8 w-full max-w-md border border-[#D4D8CC] dark:border-[#CC0000]/20 shadow-lg text-center">
|
|
{connectionTimeout ? (
|
|
<>
|
|
<h1 className="text-2xl font-bold text-[#2D3020] dark:text-white mb-3">This call is no longer available.</h1>
|
|
</>
|
|
) : !isReady ? (
|
|
<>
|
|
<div className="flex flex-col items-center gap-4 py-6">
|
|
<Loader2 className="h-8 w-8 animate-spin text-[#C84B4B]" />
|
|
<p className="text-[#8A9078] text-sm">Connecting to call...</p>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<>
|
|
<h1 className="text-2xl font-bold text-[#2D3020] dark:text-white mb-3">Join Call</h1>
|
|
{micError && (
|
|
<p className="text-[#C84B4B] text-xs mb-4">{micError}</p>
|
|
)}
|
|
<input
|
|
type="text"
|
|
value={displayName}
|
|
onChange={(e) => setDisplayName(e.target.value)}
|
|
onKeyDown={(e) => { if (e.key === "Enter") handleJoin() }}
|
|
placeholder="Enter your name"
|
|
className="bg-[#FAFAF6] dark:bg-[#1A1A1A] border border-[#D4D8CC] dark:border-[#333333] text-[#2D3020] dark:text-white placeholder-[#8A9078] dark:placeholder-[#555555] rounded-xl px-4 py-2.5 text-sm w-full mb-4 outline-none transition-colors focus:border-[#C84B4B] dark:focus:border-[#FF4444]"
|
|
/>
|
|
<button
|
|
onClick={handleJoin}
|
|
disabled={!displayName.trim()}
|
|
className="w-full bg-[#C84B4B] hover:bg-[#C84B4B]/80 disabled:bg-[#8A9078] disabled:cursor-not-allowed dark:bg-[#FF1111] dark:hover:bg-[#CC0000] dark:disabled:bg-[#333333] text-white font-semibold text-sm rounded-xl py-2.5 flex items-center justify-center gap-2 transition-all duration-200"
|
|
>
|
|
<Phone className="h-4 w-4" />
|
|
Join Call
|
|
</button>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-[#C84B4B]/10 to-[#C84B4B]/10 p-4">
|
|
<div className="bg-white dark:bg-[#141414] rounded-2xl p-8 w-full max-w-md border border-[#D4D8CC] dark:border-[#CC0000]/20 shadow-lg text-center">
|
|
{callState === "calling" && (
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-[#2D3020] dark:text-white mb-4">Calling</h1>
|
|
<p className="text-[#8A9078] text-sm animate-pulse">Connecting to call room...</p>
|
|
<div className="flex justify-center mt-6">
|
|
<button onClick={handleEnd} className="w-14 h-14 rounded-full bg-[#C84B4B] hover:bg-[#C84B4B]/80 dark:bg-[#FF1111] flex items-center justify-center text-white font-bold transition-all duration-200">
|
|
<PhoneOff className="h-5 w-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{callState === "waiting" && (
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-[#2D3020] dark:text-white mb-4">Waiting for participant</h1>
|
|
<p className="text-[#8A9078] text-sm animate-pulse">Share the call link to invite someone...</p>
|
|
{participants.length > 0 && (
|
|
<div className="mt-4 bg-[#FAFAF6] dark:bg-[#1A1A1A] rounded-xl p-3">
|
|
<div className="flex items-center gap-2 text-xs text-[#8A9078] mb-2">
|
|
<Users className="h-3 w-3" />
|
|
Participants ({participants.length})
|
|
</div>
|
|
{participants.map((p) => (
|
|
<div key={p.id} className="text-sm text-[#2D3020] dark:text-white text-left">{p.label}</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
<div className="flex justify-center mt-6">
|
|
<button onClick={handleEnd} className="w-14 h-14 rounded-full bg-[#C84B4B] hover:bg-[#C84B4B]/80 dark:bg-[#FF1111] flex items-center justify-center text-white font-bold transition-all duration-200">
|
|
<PhoneOff className="h-5 w-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{callState === "participant_joined" && (
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-[#2D3020] dark:text-white mb-4">Participant joined</h1>
|
|
{participants.length > 0 && (
|
|
<div className="mb-4 bg-[#FAFAF6] dark:bg-[#1A1A1A] rounded-xl p-3">
|
|
<div className="flex items-center gap-2 text-xs text-[#8A9078] mb-2">
|
|
<Users className="h-3 w-3" />
|
|
Participants ({participants.length})
|
|
</div>
|
|
{participants.map((p) => (
|
|
<div key={p.id} className="text-sm text-[#2D3020] dark:text-white text-left">{p.label}</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
<p className="text-[#8A9078] text-sm animate-pulse">Connecting...</p>
|
|
<div className="flex justify-center mt-6">
|
|
<button onClick={handleEnd} className="w-14 h-14 rounded-full bg-[#C84B4B] hover:bg-[#C84B4B]/80 dark:bg-[#FF1111] flex items-center justify-center text-white font-bold transition-all duration-200">
|
|
<PhoneOff className="h-5 w-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{callState === "connecting" && (
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-[#2D3020] dark:text-white mb-4">Connecting</h1>
|
|
<p className="text-[#8A9078] text-sm animate-pulse">Establishing secure connection...</p>
|
|
<div className="flex justify-center mt-6">
|
|
<button onClick={handleEnd} className="w-14 h-14 rounded-full bg-[#C84B4B] hover:bg-[#C84B4B]/80 dark:bg-[#FF1111] flex items-center justify-center text-white font-bold transition-all duration-200">
|
|
<PhoneOff className="h-5 w-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{callState === "connected" && (
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-[#2D3020] dark:text-white mb-1">Connected</h1>
|
|
{participants.length > 0 && (
|
|
<div className="mb-4 bg-[#FAFAF6] dark:bg-[#1A1A1A] rounded-xl p-3">
|
|
<div className="flex items-center gap-2 text-xs text-[#8A9078] mb-2">
|
|
<Users className="h-3 w-3" />
|
|
Participants ({participants.length})
|
|
</div>
|
|
{participants.map((p) => (
|
|
<div key={p.id} className="text-sm text-[#2D3020] dark:text-white text-left">{p.label}</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
<div className="text-[#C84B4B] font-mono text-lg mb-6">
|
|
{formatDuration(callDuration)}
|
|
</div>
|
|
<div className="flex justify-center gap-4">
|
|
<button
|
|
onClick={toggleSpeaker}
|
|
className={`w-14 h-14 rounded-full flex items-center justify-center text-white font-bold transition-all duration-200 ${isSpeakerOn ? 'bg-[#5A8FC4] dark:bg-[#1144FF]' : 'bg-[#8A9078] dark:bg-[#333333]'}`}
|
|
>
|
|
{isSpeakerOn ? <Volume2 className="h-5 w-5" /> : <VolumeX className="h-5 w-5" />}
|
|
</button>
|
|
<button
|
|
onClick={toggleMute}
|
|
className={`w-14 h-14 rounded-full flex items-center justify-center text-white font-bold transition-all duration-200 ${isMuted ? 'bg-[#5A8FC4] dark:bg-[#1144FF]' : 'bg-[#8A9078] dark:bg-[#333333]'}`}
|
|
>
|
|
{isMuted ? <MicOff className="h-5 w-5" /> : <Mic className="h-5 w-5" />}
|
|
</button>
|
|
<button
|
|
onClick={handleEnd}
|
|
className="w-14 h-14 rounded-full bg-[#C84B4B] hover:bg-[#C84B4B]/80 dark:bg-[#FF1111] flex items-center justify-center text-white font-bold transition-all duration-200"
|
|
>
|
|
<PhoneOff className="h-5 w-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{(callState === "ended" || callState === "idle") && (
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-[#2D3020] dark:text-white mb-4">Call ended</h1>
|
|
{callDuration > 0 && (
|
|
<p className="text-[#8A9078] text-sm mb-6">Duration: {formatDuration(callDuration)}</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{error && (
|
|
<p className="text-[#C84B4B] text-xs mt-4">{error}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|