Skip to main content

Setting up the Voice channel (calls)

Let's add the CallContainer.tsx file

CallContainer.tsx
import React, { useState } from 'react';

import { Mic as MicIcon, MicOff as MicOffIcon } from '@mui/icons-material';

import {
clearContact,
destroyConnection,
holdCall,
muteAgent,
resumeCall,
unMuteAgent
} from '@neev/ccp-api';
import {
AfterContactWork,
CallControl,
ConnectionCard,
} from '@neev/ccp-react';
import { Logger } from '@neev/logger';

import styles from './Softphone.module.scss';

const log = new Logger('Softphone');

export interface ISoftPhoneProps {
contact: connect.Contact;
}

const SoftPhone = ({ contact }: ISoftPhoneProps) => {
const [onMute, setOnMute] = useState(false);

// If the conversation has ended and the contact is in ENDED state
if (contact.getState().type === connect.ContactStateType.ENDED) {
return (
<AfterContactWork
onClearClick={async () => {
try {
await clearContact(contact.getContactId());
} catch (error) {
log.error(error);
}
}}
/>
);
}

// getting the initial connection (customer) of the contact object
const initialConnection = contact.getInitialConnection();

return (
<section className={styles.container}>
{/* A card that provides basic information around the connection like duration of the call with the customer as well as showing whether connection is on Hold or not */}
{/* This card also lets you put the connection on hold/resume */}
<ConnectionCard
connection={initialConnection}
onHold={async () => {
try {
await holdCall(initialConnection);
} catch (error) {
log.error(error);
}
}}
onDestroy={async () => {
try {
await destroyConnection(initialConnection);
} catch (error) {
log.error(error);
}
}}
onResume={async () => {
try {
await resumeCall(initialConnection);
} catch (error) {
log.error(error);
}
}}
/>
{/* This section contains the agent call ops - (Un)Mute & Quick connects */}
<section className={styles.controlsContainer}>
<div className={styles.controls}>
<CallControl
icon={onMute ? <MicOffIcon /> : <MicIcon />}
label={onMute ? 'Unmute' : 'Mute'}
onControlClick={() => {
if (onMute) {
unMuteAgent();
} else {
muteAgent();
}
setOnMute((prev) => !prev);
}}
/>
</div>
</section>
</section>
);
};

export default SoftPhone;

Add some styles

Softphone.module.scss
.select {
width: 100%;
}

.panel {
margin-top: 150px;
display: grid;
place-items: center;

h4,
h3 {
margin: 0;
}

h4 {
color: grey;
}
}

.controls {
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
gap: 32px;
}

.controlsContainer,
.acwContainer {
display: grid;
place-items: center;
}

.endBtn,
.quickConnectBtn {
width: 100%;
}

Next we'll update the ContactList component

ContactList.tsx
import React, { useState } from 'react';

import { Chat as ChatIcon } from '@mui/icons-material';
import { Tab, Tabs } from '@mui/material';

import { getCustomerName } from '@neev/ccp-api';
import { useContacts } from '@neev/ccp-react';

import CallContainer from './CallContainer';

const CALL_TAB = 'call';

const ContactList = () => {
// Index of the tab which is selected
const [selectedTab, setSelectedTab] = useState<string>(CALL_TAB);

const contacts = useContacts(
{
contactStateTypes: [connect.ContactStateType.CONNECTED, connect.ContactStateType.ENDED],
monitorEvents: [
connect.ContactEvents.CONNECTED,
connect.ContactEvents.ACW,
connect.ContactEvents.ENDED,
connect.ContactEvents.DESTROYED
]
},
(newContact) => {
newContact.onConnected(() =>
setSelectedTab(
newContact.getType() === connect.ContactType.VOICE ? CALL_TAB : newContact.getContactId()
)
);
}
);

const handleChange = (event: React.SyntheticEvent, newValue: string) => {
setSelectedTab(newValue);
};

// filtering out all the chat contacts as they will appear side by side
const chatContacts = contacts.filter((contact) => contact.getType() === connect.ContactType.CHAT);

return (
<section>
<Tabs
value={selectedTab}
onChange={handleChange}
aria-label="contact tabs"
sx={{ paddingBlock: '0px', minHeight: 'unset', marginBottom: '16px' }}
>
<Tab
sx={{ paddingBlock: '10px', minHeight: 'unset' }}
label="Call"
value={CALL_TAB}
iconPosition="start"
icon={<PhoneIcon />}
/>
</Tabs>
{/* since there can only be one Call contact, there is only a single call tab button */}
{/* If it is selected, then show the CallContainer */}
<div style={{ display: selectedTab === CALL_TAB ? 'block' : 'none' }}>
<CallContainer callContact={callContact} />
</div>
</section>
);
};

export default ContactList;

We'll add some chats now.