feat: outbound call UI — immediate call card, auto-answer SIP bridge

- ClickToCallButton sets callState='ringing-out' immediately on click
- ActiveCallCard shows "Calling..." state for outbound
- SIP manager auto-answers incoming SIP when outbound is pending (Kookoo bridge)
- CallPrepCard shows lead context while dialing
- On error, resets state cleanly

Flow: Click Call → UI shows call card → Kookoo dials customer →
customer answers → SIP bridges → auto-answer → active call → disposition

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 21:29:26 +05:30
parent 1d395a8c36
commit 26b9d93f32
3 changed files with 65 additions and 4 deletions

View File

@@ -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');
}