"use client" import { useState, useEffect, useCallback } from "react" import { Phone, PhoneOff, Mic, MicOff, Volume2, VolumeX, X } from "lucide-react" import { getSupabase } from "@/lib/supabase" import { useWebRTCCall } from "@/hooks/useWebRTCCall" interface Contact { id: string name: string phone: string avatar_url: string | null } interface VoiceCallModalProps { open: boolean onClose: () => void } export default function VoiceCallModal({ open, onClose }: VoiceCallModalProps) { const [contacts, setContacts] = useState([]) const [contactsLoading, setContactsLoading] = useState(false) const [contactsError, setContactsError] = useState(false) const [searchQuery, setSearchQuery] = useState("") const [phoneNumber, setPhoneNumber] = useState("") const [phoneError, setPhoneError] = useState(false) const [lookupMessage, setLookupMessage] = useState(null) const setLookupMessageWithReset = useCallback((msg: string | null) => { setLookupMessage(msg) if (msg !== "This phone number is not registered on the platform.") { setInviteUrl(null) } }, []) const [inviteUrl, setInviteUrl] = useState(null) const [inviteLoading, setInviteLoading] = useState(false) const generateInvite = useCallback(async (phone: string) => { setInviteLoading(true) try { const res = await fetch("/api/invite/generate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ phone }), }) const data = await res.json() if (data.inviteUrl) { setInviteUrl(data.inviteUrl) } } catch { // fallback: generate client-side const token = crypto.randomUUID?.() || Math.random().toString(36).slice(2) setInviteUrl(`${window.location.origin}/join/${token}`) } finally { setInviteLoading(false) } }, []) const { isReady, isCallActive, isIncoming, isMuted, isSpeakerOn, callState, callDuration, error: callError, callerInfo, makeCall, acceptCall, rejectCall, endCall, toggleMute, toggleSpeaker, formatDuration, } = useWebRTCCall() useEffect(() => { if (!open && callState !== "idle") { endCall() } }, [open, callState, endCall]) useEffect(() => { if (!open) { setSearchQuery("") setPhoneNumber("") setPhoneError(false) return } const fetchContacts = async () => { setContactsLoading(true) setContactsError(false) try { const sb = getSupabase() const { data, error } = await sb .from("contacts") .select("id, name, phone, avatar_url") .order("name", { ascending: true }) if (error) throw error setContacts(data || []) } catch { setContactsError(true) } finally { setContactsLoading(false) } } fetchContacts() }, [open]) useEffect(() => { if (!open) return const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") onClose() } document.addEventListener("keydown", handleKeyDown) return () => document.removeEventListener("keydown", handleKeyDown) }, [open, onClose]) const filteredContacts = contacts.filter( (c) => c.name.toLowerCase().includes(searchQuery.toLowerCase()) || c.phone.toLowerCase().includes(searchQuery.toLowerCase()), ) if (!open) return null return (
{ if (e.target === e.currentTarget) onClose() }} >

Start a Call

Search contacts or dial a number

CONTACTS

setSearchQuery(e.target.value)} placeholder="Search contacts..." className="bg-[#F5F5F5] dark:bg-[#1A1A1A] border border-[#E0E0E0] dark:border-[#333333] text-[#111111] dark:text-white placeholder-[#AAAAAA] dark:placeholder-[#555555] rounded-xl px-4 py-2.5 text-sm w-full mb-3 outline-none transition-colors focus:border-[#CC0000] dark:focus:border-[#FF4444]" />
{contactsLoading && (

Loading contacts...

)} {contactsError && (

Could not load contacts

)} {!contactsLoading && !contactsError && filteredContacts.length === 0 && (

No contacts found

)} {!contactsLoading && !contactsError && filteredContacts.map((contact) => (
{contact.avatar_url ? ( {contact.name} ) : ( contact.name.charAt(0).toUpperCase() )}

{contact.name}

{contact.phone}

))}

OR

DIAL A NUMBER

{ setPhoneNumber(e.target.value); setPhoneError(false) }} placeholder="+27 000 000 0000" className="bg-[#F5F5F5] dark:bg-[#1A1A1A] border border-[#E0E0E0] dark:border-[#333333] text-[#111111] dark:text-white placeholder-[#AAAAAA] dark:placeholder-[#555555] rounded-xl px-4 py-2.5 text-sm w-full outline-none transition-colors focus:border-[#CC0000] dark:focus:border-[#FF4444]" /> {phoneError && (

Please enter a phone number

)} {isIncoming ? (
Incoming Call
{callerInfo?.firstName?.charAt(0).toUpperCase()}

{callerInfo?.firstName} {callerInfo?.lastName}

{callerInfo?.username}

Ringing...

) : isCallActive ? (
{callState === "calling" && ( <>
{phoneNumber}

Calling...

)} {callState === "ringing" && ( <>
{phoneNumber}

Ringing...

)} {callState === "connecting" && ( <>
{phoneNumber}

Connecting...

)} {callState === "connected" && ( <>
{phoneNumber}
{formatDuration(callDuration)}
)} {(callState === "ended" || callState === "missed" || callState === "rejected" || callState === "offline" || callState === "busy") && ( <>
{phoneNumber}

{callState === "missed" && "This phone number is not registered on the platform."} {callState === "offline" && "User is offline"} {callState === "rejected" && "Call rejected"} {callState === "busy" && "User is busy"} {callState === "ended" && "Call ended"}

{callState === "missed" && (
{!inviteUrl ? ( ) : (

{inviteUrl}

)}
)} )}
) : ( <> {lookupMessage ? (

{lookupMessage}

{lookupMessage === "This phone number is not registered on the platform." && (
{!inviteUrl ? ( ) : (

{inviteUrl}

)}
)}
) : ( <> {callError && (

{callError}

)} )} )}
) }