Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Outbound Dial Labels and/or Strings
export const OutdialStrings = {
ANI_SELECT_LABEL: 'Outdial ANI',
ANI_SELECT_PLACEHOLDER: 'Enter Outdial ANI',
CALL_BUTTON_ARIA_LABEL: 'Start Outdial Call',
DIALPAD_LABEL: 'Dialpad',
DN_PLACEHOLDER: 'Enter number to dial',
INCORRECT_DN_FORMAT: 'Incorrect format.',
OUTDIAL_CALL: 'Outdial Call',
};

// Utility Constants
export const KEY_LIST = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'];
Original file line number Diff line number Diff line change
@@ -1,67 +1,43 @@
.out-dial-call-box {
position: relative;
width: 100%;
background-color: var(--mds-color-theme-background-primary-normal);
border-radius: 0.5rem;
box-shadow: var(--mds-shadow-2);
padding: 1.25rem;
max-width: 50rem;
}

.out-dial-call-section-box {
padding: 0.625rem;
}

.out-dial-call-fieldset {
border: 1px solid var(--mds-color-theme-outline-secondary-normal);
border-radius: 0.325rem;
padding: 0.625rem;
margin-bottom: 1.25rem;
}

.out-dial-call-legend-box {
font-weight: bold;
color: var(--mds-color-theme-text-primary-normal);
}

.keypad {
display: flex;
flex-direction: column;
align-items: center;
background: var(--mds-color-theme-background-secondary-normal);
padding: 1.25rem;
border-radius: 0.625rem;
width: 15.625rem;
padding: 1rem;

mdc-input {
padding-bottom: 0; // default is 1 rem, 1.5 rem needed but provided by .keys
}

#outdial-tablist {
margin-bottom: 1rem;
}

input {
.input {
width: 100%;
padding: 0.625rem;
text-align: center;
font-size: 1.125rem;
margin-bottom: 0.625rem;
background: var(--mds-color-theme-background-primary-normal);
border: 1px solid var(--mds-color-theme-outline-secondary-normal);
border-radius: 0.25rem;
color: var(--mds-color-theme-text-primary-normal);
}

.button {
width: 2.5rem;
height: 2.5rem;
}

.keys {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.625rem;
row-gap: 1rem;
column-gap: 1.5rem;
padding: 1.5rem 0;
list-style-type: none;
margin: 0;
}

.key {
width: 3.75rem;
height: 3.75rem;
display: flex;
align-items: center;
justify-content: center;
background: var(--mds-color-theme-background-primary-normal);
background: var(--mds-color-theme-background-alert-default-normal);
color: var(--mds-color-theme-text-primary-normal);
font-size: 1.25rem;
border-radius: 50%;
cursor: pointer;

transition: background-color 0.2s ease;

&:hover {
Expand All @@ -72,22 +48,4 @@
background: var(--mds-color-theme-background-primary-active);
}
}
}

.out-dial-call-btn {
margin-top: 0.625rem;
background: var(--mds-color-theme-background-alert-success-normal);
color: var(--mds-color-theme-text-primary-normal);
padding: 0.625rem 1.25rem;
border-radius: 50%;
cursor: pointer;
transition: background-color 0.2s ease;

&:hover {
background: var(--mds-color-theme-background-alert-success-hover);
}

&:active {
background: var(--mds-color-theme-background-alert-success-active);
}
}
Original file line number Diff line number Diff line change
@@ -1,52 +1,153 @@
import React, {useState} from 'react';
import React, {useMemo, useState} from 'react';
import {OutdialCallComponentProps} from '../task.types';
import './outdial-call.style.scss';
import {withMetrics} from '@webex/cc-ui-logging';
import {Input, Button, Option, Select, Tab} from '@momentum-design/components/dist/react';
import {OutdialStrings, KEY_LIST} from './constants';

/**
* @interface OutdialANIEntry
* Interface representing an ANI (Automatic Number Identification) entry returned by the
* List Outdial ANI Entries API call.
*
* @property {string} organizationId - The organization ID the ANI is associated with.
* @property {string} id - ID of this contact center ANI entry.
* @property {number} version - The version number of the ANI entry.
* @property {string} name - The name assigned to the ANI entry.
* @property {string} number - The phone number associated with the ANI entry.
* @property {number} createdTime - The timestamp(in epoch milliseconds) when the ANI entry was created.
* @property {number} lastUpdatedTime - The timestamp(in epoch milliseconds) when the ANI entry was last updated.
*/
interface OutdialANIEntry {
organizationId?: string;
id?: string;
version?: number;
name: string;
number: string;
createdTime?: number;
lastUpdatedTime?: number;
}
// Note: Only 'name' and 'number' are needed in this component.

/**
* OutdialCallComponent renders a dialpad UI for agents to initiate outbound calls.
* It allows input of a destination number, selection of an ANI, and validates input.
*
* This component provides a keypad interface for entering a destination number, validates the input,
* allows selection of an ANI (Automatic Number Identification), and triggers an outbound call action.
*
* @param props - Properties for the OutdialCallComponent.
* @property startOutdial - Function to initiate the outdial call with the entered destination number.
*/
const OutdialCallComponent: React.FunctionComponent<OutdialCallComponentProps> = (props) => {
const {startOutdial} = props;
const {startOutdial, outdialANIEntries} = props;

// State Hooks
const [destination, setDestination] = useState('');
const [isValidNumber, setIsValidNumber] = useState('');
const [selectedANI, setSelectedANI] = useState('');

// Validate the input format using regex from agent desktop
const regExForDnSpecialChars = useMemo(
() => new RegExp('^[+1][0-9]{3,18}$|^[*#][+1][0-9*#:]{3,18}$|^[0-9*#]{3,18}$'),
[]
);

// Give Select an empty list if outdial ANI entries are not provided
const outdialANIList: OutdialANIEntry[] = outdialANIEntries ?? [];

const updateOutboundNumber = (e: React.ChangeEvent<HTMLInputElement>) => {
// Allow only valid input that is digits, #, *, and +
const VALID_KEYPAD_CHARS = /[\d#*+]/g;
const filteredValue = e.target.value.match(VALID_KEYPAD_CHARS)?.join('') || '';
setDestination(filteredValue);
/**
* validateOutboundNumber
* @param e The input change event
* If the input is invalid, sets an error message on dialnumber input
*/
const validateOutboundNumber = (value: string) => {
if (value && !regExForDnSpecialChars.test(value)) {
setIsValidNumber(OutdialStrings.INCORRECT_DN_FORMAT);
} else {
setIsValidNumber('');
}
};

// Function to press a key on the outdial keypad.
const handelKeyPress = (value: string) => {
setDestination((prev) => prev + value);
/**
* handleKeyPress
* @param value The key value pressed
* Appends the pressed key to the destination input field
*/
const handleKeyPress = (value: string) => {
setDestination(destination + value);
validateOutboundNumber(destination + value);
};

return (
<div className="out-dial-call-box">
<section className="out-dial-call-section-box">
<fieldset className="out-dial-call-fieldset">
<legend className="out-dial-call-legend-box">Outdial Call</legend>
<div className="keypad">
<input
onChange={updateOutboundNumber}
id="outBoundDialNumber"
placeholder="Enter number to dial"
value={destination}
/>
<div className="keys">
{['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'].map((key) => (
<div key={key} className="key" onClick={() => handelKeyPress(key)}>
{key}
</div>
))}
</div>
<button className="out-dial-call-btn" onClick={() => startOutdial(destination)}>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24">
<path d="M6.62 10.79a15.053 15.053 0 006.59 6.59l2.2-2.2a1 1 0 011.11-.27c1.12.45 2.33.69 3.58.69a1 1 0 011 1v3.5a1 1 0 01-1 1C10.29 21 3 13.71 3 4.5a1 1 0 011-1h3.5a1 1 0 011 1c0 1.25.24 2.46.69 3.58a1 1 0 01-.27 1.11l-2.2 2.2z" />
</svg>
</button>
</div>
</fieldset>
</section>
</div>
<article className="keypad">
<header role="tablist" id="outdial-tablist">
<Tab
active={true}
text={OutdialStrings.DIALPAD_LABEL}
iconName={'dialpad-bold'}
tabId="dialpad-tab"
variant="pill"
aria-controls="dialpad-panel"
></Tab>
</header>
<Input
className="input"
id="outdial-number-input"
name="outdial-number-input"
data-testid="outdial-number-input"
helpText={isValidNumber}
helpTextType={isValidNumber ? 'error' : 'default'}
placeholder={OutdialStrings.DN_PLACEHOLDER}
value={destination}
onChange={(e: unknown) => {
const inputValue = (e as React.ChangeEvent<HTMLInputElement>).target.value;
setDestination(inputValue);
validateOutboundNumber(inputValue);
}}
/>
<ul className="keys">
{KEY_LIST.map((key) => (
<li key={key}>
<Button className="key button" onClick={() => handleKeyPress(key)}>
{key}
</Button>
</li>
))}
</ul>
<Select
className="input"
label={OutdialStrings.ANI_SELECT_LABEL}
id="outdial-ani-option"
name="outdial-ani-option-select"
data-testid="outdial-ani-option-select"
placeholder={OutdialStrings.ANI_SELECT_PLACEHOLDER}
onChange={(event: CustomEvent) => {
setSelectedANI(event.detail.value);
}}
>
{outdialANIList.map((option: OutdialANIEntry, index: number) => {
return (
<Option
selected={option.number === selectedANI}
key={index}
value={option.number}
name={`outdial-ani-option-${index}`}
data-testid={`outdial-ani-option-${index}`}
>
{option.name}
</Option>
);
})}
</Select>
<Button
data-testid="outdial-call-button"
className="button"
prefixIcon={'handset-regular'}
onClick={() => startOutdial(destination)}
disabled={!!isValidNumber || !destination}
/>
</article>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,20 @@ export interface OutdialCallProps {
*/
startOutdial: (destination: string) => void;

/**
* Array of Outdial ANI entries.
* TODO: update with exported type when SDK PR#4513 is merged
*/
outdialANIEntries?: Array<{
organizationId?: string;
id?: string;
version?: number;
name: string;
number: string;
createdTime?: number;
lastUpdatedTime?: number;
}>;

/**
* CC SDK Instance.
*/
Expand All @@ -476,7 +490,7 @@ export interface OutdialCallProps {
logger: ILogger;
}

export type OutdialCallComponentProps = Pick<OutdialCallProps, 'startOutdial'>;
export type OutdialCallComponentProps = Pick<OutdialCallProps, 'startOutdial' | 'outdialANIEntries'>;

/**
* Interface representing the properties for CallControlListItem component.
Expand Down
Loading