diff --git a/src/components/call-desk/active-call-card.tsx b/src/components/call-desk/active-call-card.tsx index b0d1b86..7da9236 100644 --- a/src/components/call-desk/active-call-card.tsx +++ b/src/components/call-desk/active-call-card.tsx @@ -105,7 +105,33 @@ export const ActiveCallCard = ({ lead, callerPhone }: ActiveCallCardProps) => { setCallerNumber(null); }; - // Ringing state + // Outbound ringing — agent initiated the call + if (callState === 'ringing-out') { + return ( +
+
+
+
+
+ +
+
+
+

Calling...

+

{fullName || phoneDisplay}

+ {fullName &&

{phoneDisplay}

} +
+
+
+ +
+
+ ); + } + + // Inbound ringing if (callState === 'ringing-in') { return (
diff --git a/src/components/call-desk/click-to-call-button.tsx b/src/components/call-desk/click-to-call-button.tsx index 0532a6f..7be395f 100644 --- a/src/components/call-desk/click-to-call-button.tsx +++ b/src/components/call-desk/click-to-call-button.tsx @@ -1,7 +1,10 @@ import { useState } from 'react'; import { Phone01 } from '@untitledui/icons'; +import { useSetAtom } from 'jotai'; import { Button } from '@/components/base/buttons/button'; import { useSip } from '@/providers/sip-provider'; +import { sipCallStateAtom, sipCallerNumberAtom } from '@/state/sip-state'; +import { setOutboundPending } from '@/state/sip-manager'; import { apiClient } from '@/lib/api-client'; import { notify } from '@/lib/toast'; @@ -15,14 +18,25 @@ interface ClickToCallButtonProps { export const ClickToCallButton = ({ phoneNumber, leadId, label, size = 'sm' }: ClickToCallButtonProps) => { const { isRegistered, isInCall } = useSip(); const [dialing, setDialing] = useState(false); + const setCallState = useSetAtom(sipCallStateAtom); + const setCallerNumber = useSetAtom(sipCallerNumberAtom); const handleDial = async () => { setDialing(true); + + // Immediately show the call UI and mark outbound pending for auto-answer + setCallState('ringing-out'); + setCallerNumber(phoneNumber); + setOutboundPending(true); + try { await apiClient.post('/api/ozonetel/dial', { phoneNumber, leadId }); - notify.success('Dialing', `Calling ${phoneNumber}...`); } catch { - // apiClient.post already toasts the error + // API error — reset call state + setCallState('idle'); + setCallerNumber(null); + setOutboundPending(false); + notify.error('Dial Failed', 'Could not place the call'); } finally { setDialing(false); } @@ -37,7 +51,7 @@ export const ClickToCallButton = ({ phoneNumber, leadId, label, size = 'sm' }: C isDisabled={!isRegistered || isInCall || !phoneNumber || dialing} isLoading={dialing} > - {dialing ? 'Dialing...' : (label ?? 'Call')} + {label ?? 'Call'} ); }; diff --git a/src/state/sip-manager.ts b/src/state/sip-manager.ts index a38b145..e3f3214 100644 --- a/src/state/sip-manager.ts +++ b/src/state/sip-manager.ts @@ -4,6 +4,7 @@ import type { SIPConfig, ConnectionStatus, CallState } from '@/types/sip'; // Singleton SIP client — survives React StrictMode remounts let sipClient: SIPClient | null = null; let connected = false; +let outboundPending = false; type StateUpdater = { setConnectionStatus: (status: ConnectionStatus) => void; @@ -17,6 +18,14 @@ export function registerSipStateUpdater(updater: StateUpdater) { stateUpdater = updater; } +export function setOutboundPending(pending: boolean) { + outboundPending = pending; +} + +export function isOutboundPending(): boolean { + return outboundPending; +} + export function connectSip(config: SIPConfig): void { if (connected || sipClient?.isRegistered() || sipClient?.isConnected()) { return; @@ -38,6 +47,17 @@ export function connectSip(config: SIPConfig): void { config, (status) => stateUpdater?.setConnectionStatus(status), (state, number) => { + // Auto-answer SIP when it's a bridge from our outbound Kookoo call + if (state === 'ringing-in' && outboundPending) { + outboundPending = false; + // Auto-answer after a brief delay to let SIP negotiate + setTimeout(() => { + sipClient?.answer(); + stateUpdater?.setCallState('active'); + }, 500); + return; + } + stateUpdater?.setCallState(state); if (number !== undefined) stateUpdater?.setCallerNumber(number ?? null); }, @@ -50,6 +70,7 @@ export function disconnectSip(): void { sipClient?.disconnect(); sipClient = null; connected = false; + outboundPending = false; stateUpdater?.setConnectionStatus('disconnected'); }