mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-12 02:38:15 +00:00
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:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user