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

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