From 7d8e32238f278745b3e6f58a20e0d9c3a732bb33 Mon Sep 17 00:00:00 2001 From: molker Date: Mon, 5 Jan 2026 15:04:55 -0500 Subject: [PATCH 1/3] Update OutdialCall CSS --- .../components/task/OutdialCall/constants.ts | 5 + .../task/OutdialCall/outdial-call.style.scss | 44 +- .../task/OutdialCall/outdial-call.tsx | 41 +- .../out-dial-call.snapshot.tsx.snap | 5509 ++++++++--------- .../task/OutdialCall/out-dial-call.tsx | 4 +- .../task/tests/OutdialCall/index.tsx | 6 - 6 files changed, 2769 insertions(+), 2840 deletions(-) diff --git a/packages/contact-center/cc-components/src/components/task/OutdialCall/constants.ts b/packages/contact-center/cc-components/src/components/task/OutdialCall/constants.ts index c4133a2fa..71bfd35b2 100644 --- a/packages/contact-center/cc-components/src/components/task/OutdialCall/constants.ts +++ b/packages/contact-center/cc-components/src/components/task/OutdialCall/constants.ts @@ -7,7 +7,12 @@ export const OutdialStrings = { INCORRECT_DN_FORMAT: 'Incorrect format.', OUTDIAL_CALL: 'Outdial Call', ADDRESS_BOOK_SEARCH_PLACEHOLDER: 'Search by Name/Number', + ADDRESS_BOOK_NO_RESULTS: 'No address book entries found.', }; // Utility Constants export const KEY_LIST = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#']; +export const TABS = { + DIAL_PAD: 'dial_pad', + ADDRESS_BOOK: 'address_book', +}; diff --git a/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.style.scss b/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.style.scss index 2a0cf5046..3d0183824 100644 --- a/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.style.scss +++ b/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.style.scss @@ -3,12 +3,7 @@ flex-direction: column; align-items: center; border-radius: 0.625rem; - width: 15.625rem; - height: 26.5rem; - - .keypad { - width: 100%; - } + width: 16.5rem; mdc-input { padding-bottom: 0; // default is 1 rem, 1.5 rem needed but provided by .keys @@ -21,16 +16,13 @@ .outdial-ani-select-container { position: relative; width: 100%; - } - - .outDialCallButton { - margin-top: 1.5rem; + padding-bottom: 1rem; } .outdial-select-arrow-icon { position: absolute; - right: 0; - top: 83%; + right: 0.375rem; + top: 71%; transform: translateY(-50%); display: flex; align-items: center; @@ -38,6 +30,13 @@ z-index: 1; } + #outdial-ani-option-select { + width: 100%; + label { + margin: 0; + } + } + .outdial-ani-option-name { display: flex; align-items: center; @@ -51,7 +50,7 @@ column-gap: 1.5rem; padding: 1.5rem 0; list-style-type: none; - margin: 0 20%; + margin: 0; } .key { @@ -70,6 +69,13 @@ } } + .dialpad-tab-container { + display: flex; + flex-direction: column; + align-items: center; + width: 16.5rem; + } + .address-book-container { width: 100%; } @@ -116,6 +122,7 @@ } .tab-list { + padding-bottom: 1rem; mdc-button { display: none; } @@ -125,16 +132,5 @@ padding: 0; } } - - .ani-select-input { - margin-top: 1rem; - } -} - -.height-auto { - height: auto; } -.height-28-5rem { - height: 28.5rem; -} diff --git a/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.tsx b/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.tsx index 40c14f9ab..87ca2f742 100644 --- a/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.tsx +++ b/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.tsx @@ -9,7 +9,7 @@ import {SelectNext} from '@momentum-ui/react-collaboration'; import {Item} from '@react-stately/collections'; import {OutdialAniEntry, OutdialCallComponentProps} from '../task.types'; -import {OutdialStrings, KEY_LIST} from './constants'; +import {OutdialStrings, KEY_LIST, TABS} from './constants'; import {DEFAULT_PAGE_SIZE} from '../constants'; import {createInitials, debounce} from '../CallControl/CallControlCustom/call-control-custom.utils'; import {useIntersectionObserver} from '../../../hooks'; @@ -37,11 +37,6 @@ const OutdialCallComponent: React.FunctionComponent = isAddressBookEnabled = true, } = props; - const TABS = { - DIAL_PAD: 'dial_pad', - ADDRESS_BOOK: 'address_book', - }; - // State Hooks const [selectedTab, setSelectedTab] = useState(TABS.DIAL_PAD); const [destination, setDestination] = useState(''); @@ -140,7 +135,7 @@ const OutdialCallComponent: React.FunctionComponent = } }; - const handleDiapadTabClick = () => { + const handleDialpadTabClick = () => { setSelectedAddressBookEntry(null); // Don't clear destination - preserve selected address book entry number setSelectedTab(TABS.DIAL_PAD); @@ -252,16 +247,16 @@ const OutdialCallComponent: React.FunctionComponent = ) : ( -

No address book entries found.

+

{OutdialStrings.ADDRESS_BOOK_NO_RESULTS}

)} ); }; - const renderDiapad = () => { + const renderDialpad = () => { return ( -
+ <> =
    {KEY_LIST.map((key) => (
  • -
  • ))}
-
+ ); }; return ( -
+
{isAddressBookEnabled && ( <> @@ -308,29 +303,34 @@ const OutdialCallComponent: React.FunctionComponent = tabId={TABS.DIAL_PAD} aria-controls={TABS.DIAL_PAD} variant="glass" - onClick={handleDiapadTabClick} + onClick={handleDialpadTabClick} > {selectedTab === TABS.ADDRESS_BOOK && ( -
{renderAddressBook()} -
+ )} {selectedTab === TABS.DIAL_PAD && ( -
- {renderDiapad()} -
+
+ {renderDialpad()} +
)} )} - {!isAddressBookEnabled && renderDiapad()} + {!isAddressBookEnabled && renderDialpad()}
= +
+ + +
+ +`; + +exports[`Outdial Call Component calls startOutdial with correct payload when clicking call button 1`] = ` +
+
+ +
    +
  • + + 1 + +
  • +
  • + + 2 + +
  • +
  • + + 3 + +
  • +
  • + + 4 + +
  • +
  • + + 5 + +
  • +
  • + + 6 + +
  • +
  • + + 7 + +
  • +
  • + + 8 + +
  • +
  • + + 9 + +
  • +
  • + + * + +
  • +
  • + + 0 + +
  • +
  • + + # + +
  • +
@@ -4067,7 +4342,7 @@ exports[`Outdial Call Component allows special characters (* # +) from keypad 1` style="--local-width: 100%;" >
@@ -4165,197 +4437,193 @@ exports[`Outdial Call Component allows special characters (* # +) from keypad 1` `; -exports[`Outdial Call Component calls startOutdial with correct payload when clicking call button 1`] = ` +exports[`Outdial Call Component does not allow empty input 1`] = `
-
+
    - -
      -
    • - - 1 - -
    • -
    • - - 2 - -
    • -
    • - - 3 - -
    • -
    • - - 4 - -
    • -
    • - - 5 - -
    • -
    • - - 6 - -
    • -
    • - - 7 - -
    • -
    • - - 8 - -
    • -
    • - - 9 - -
    • -
    • - - * - -
    • -
    • - - 0 - -
    • -
    • - - # - -
    • -
    -
+
  • + + 1 + +
  • +
  • + + 2 + +
  • +
  • + + 3 + +
  • +
  • + + 4 + +
  • +
  • + + 5 + +
  • +
  • + + 6 + +
  • +
  • + + 7 + +
  • +
  • + + 8 + +
  • +
  • + + 9 + +
  • +
  • + + * + +
  • +
  • + + 0 + +
  • +
  • + + # + +
  • +
    @@ -4371,7 +4639,7 @@ exports[`Outdial Call Component calls startOutdial with correct payload when cli style="--local-width: 100%;" >
    @@ -4467,213 +4736,209 @@ exports[`Outdial Call Component calls startOutdial with correct payload when cli `; -exports[`Outdial Call Component does not allow empty input 1`] = ` +exports[`Outdial Call Component does not allow invalid characters when typing 1`] = `
    -
    +
      - + + 1 + + +
    • + + 2 + +
    • +
    • + + 3 + +
    • +
    • + + 4 + +
    • +
    • + + 5 + +
    • +
    • + + 6 + +
    • +
    • + + 7 + +
    • +
    • + + 8 + +
    • +
    • + + 9 + +
    • +
    • + + * + +
    • +
    • + + 0 + +
    • +
    • + + # + +
    • +
    +
    + -
      -
    • - - 1 - -
    • -
    • - - 2 - -
    • -
    • - - 3 - -
    • -
    • - - 4 - -
    • -
    • - - 5 - -
    • -
    • - - 6 - -
    • -
    • - - 7 - -
    • -
    • - - 8 - -
    • -
    • - - 9 - -
    • -
    • - - * - -
    • -
    • - - 0 - -
    • -
    • - - # - -
    • -
    -
    -
    - -
    `; -exports[`Outdial Call Component does not allow invalid characters when typing 1`] = ` +exports[`Outdial Call Component has no ANI entry options when the entry list is empty 1`] = `
    -
    +
      - + + 1 + + +
    • + + 2 + +
    • +
    • + + 3 + +
    • +
    • + + 4 + +
    • +
    • + + 5 + +
    • +
    • + + 6 + +
    • +
    • + + 7 + +
    • +
    • + + 8 + +
    • +
    • + + 9 + +
    • +
    • + + * + +
    • +
    • + + 0 + +
    • +
    • + + # + +
    • +
    +
    + -
      -
    • - - 1 - -
    • -
    • - + - 2 - -
    • -
    • - - 3 - -
    • -
    • - - 4 - -
    • -
    • - - 5 - -
    • -
    • - - 6 - -
    • -
    • - - 7 - -
    • -
    • - - 8 - -
    • -
    • - - 9 - -
    • -
    • - - * - -
    • -
    • - - 0 - -
    • -
    • - + + +
    • -
    -
    + Enter Outdial ANI + +
    + +
    +
    +`; + +exports[`Outdial Call Component sets selected ani when an option is selected 1`] = ` +
    +
    + +
      +
    • + + 1 + +
    • +
    • + + 2 + +
    • +
    • + + 3 + +
    • +
    • + + 4 + +
    • +
    • + + 5 + +
    • +
    • + + 6 + +
    • +
    • + + 7 + +
    • +
    • + + 8 + +
    • +
    • + + 9 + +
    • +
    • + + * + +
    • +
    • + + 0 + +
    • +
    • + + # + +
    • +
    -
    -
    - -
    - -`; - -exports[`Outdial Call Component has no ANI entry options when the entry list is empty 1`] = ` -
    -
    -
    - -
      -
    • - - 1 - -
    • -
    • - - 2 - -
    • -
    • - - 3 - -
    • -
    • - - 4 - -
    • -
    • - - 5 - -
    • -
    • - - 6 - -
    • -
    • - - 7 - -
    • -
    • - - 8 - -
    • -
    • - - 9 - -
    • -
    • - - * - -
    • -
    • - - 0 - -
    • -
    • - - # - -
    • -
    -
    -
    - -
    - - - -
    -
    -
    - -
    -
    -`; - -exports[`Outdial Call Component sets selected ani when an option is selected 1`] = ` -
    -
    -
    - -
      -
    • - - 1 - -
    • -
    • - - 2 - -
    • -
    • - - 3 - -
    • -
    • - - 4 - -
    • -
    • - - 5 - -
    • -
    • - - 6 - -
    • -
    • - - 7 - -
    • -
    • - - 8 - -
    • -
    • - - 9 - -
    • -
    • - - * - -
    • -
    • - - 0 - -
    • -
    • - - # - -
    • -
    -
    -
    - -
    - - -
    - -
    -
    -`; - -exports[`Outdial Call Component shows error help text when invalid characters are entered 1`] = ` -
    -
    -
    - -
      -
    • - - 1 - -
    • -
    • - - 2 - -
    • -
    • - - 3 - -
    • -
    • - - 4 - -
    • -
    • - - 5 - -
    • -
    • - - 6 - -
    • -
    • - - 7 - -
    • -
    • - - 8 - -
    • -
    • - - 9 - -
    • -
    • - - * - -
    • -
    • - - 0 - -
    • -
    • - - # - -
    • -
    -
    + /> +
    + + + + +`; + +exports[`Outdial Call Component shows error help text when invalid characters are entered 1`] = ` +
    +
    + +
      +
    • + + 1 + +
    • +
    • + + 2 + +
    • +
    • + + 3 + +
    • +
    • + + 4 + +
    • +
    • + + 5 + +
    • +
    • + + 6 + +
    • +
    • + + 7 + +
    • +
    • + + 8 + +
    • +
    • + + 9 + +
    • +
    • + + * + +
    • +
    • + + 0 + +
    • +
    • + + # + +
    • +
    @@ -6058,228 +6004,223 @@ exports[`Outdial Call Component shows error help text when invalid characters ar >
    -
    - ⃞ -
    -
    - - -
    -
    - - - -`; - -exports[`Outdial Call Component updates input value when clicking keypad buttons 1`] = ` -
    -
    -
    - -
      -
    • - - 1 - -
    • -
    • - - 2 - -
    • -
    • - - 3 - -
    • -
    • - - 4 - -
    • -
    • - - 5 - -
    • -
    • - - 6 - -
    • -
    • - - 7 - -
    • -
    • - - 8 - -
    • -
    • - - 9 - -
    • -
    • - - * - -
    • -
    • - - 0 - -
    • -
    • - - # - -
    • -
    -
    + > +
    + ⃞ +
    +
    + + + + + + + +`; + +exports[`Outdial Call Component updates input value when clicking keypad buttons 1`] = ` +
    +
    + +
      +
    • + + 1 + +
    • +
    • + + 2 + +
    • +
    • + + 3 + +
    • +
    • + + 4 + +
    • +
    • + + 5 + +
    • +
    • + + 6 + +
    • +
    • + + 7 + +
    • +
    • + + 8 + +
    • +
    • + + 9 + +
    • +
    • + + * + +
    • +
    • + + 0 + +
    • +
    • + + # + +
    • +
    @@ -6377,7 +6318,6 @@ exports[`Outdial Call Component updates input value when clicking keypad buttons
    -
    +
      - -
        -
      • - - 1 - -
      • -
      • - - 2 - -
      • -
      • - - 3 - -
      • -
      • - - 4 - -
      • -
      • - - 5 - -
      • -
      • - - 6 - -
      • -
      • - - 7 - -
      • -
      • - - 8 - -
      • -
      • - - 9 - -
      • -
      • - - * - -
      • -
      • - - 0 - -
      • -
      • - - # - -
      • -
      -
    +
  • + + 1 + +
  • +
  • + + 2 + +
  • +
  • + + 3 + +
  • +
  • + + 4 + +
  • +
  • + + 5 + +
  • +
  • + + 6 + +
  • +
  • + + 7 + +
  • +
  • + + 8 + +
  • +
  • + + 9 + +
  • +
  • + + * + +
  • +
  • + + 0 + +
  • +
  • + + # + +
  • +
    @@ -6681,7 +6617,6 @@ exports[`Outdial Call Component updates input value when typing directly 1`] = `
    { render(); const article = await screen.findByTestId('outdial-call-container'); expect(article).toBeInTheDocument(); - expect(article).toHaveClass('keypad'); + expect(article).toHaveClass('outdial-container'); }); it('dial number input', async () => { @@ -59,7 +59,7 @@ describe('OutdialCallComponent', () => { const keypadKeys = within(keypadContainer).getAllByRole('button'); expect(keypadKeys).toHaveLength(KEY_LIST.length); keypadKeys.forEach((button) => { - expect(button).toHaveClass('key button'); + expect(button).toHaveClass('key'); expect(button).toHaveAttribute('color', 'default'); expect(button).toHaveAttribute('data-btn-type', 'pill'); expect(button).toHaveAttribute('size', '32'); diff --git a/packages/contact-center/task/tests/OutdialCall/index.tsx b/packages/contact-center/task/tests/OutdialCall/index.tsx index 496d52fe8..7b4564e61 100644 --- a/packages/contact-center/task/tests/OutdialCall/index.tsx +++ b/packages/contact-center/task/tests/OutdialCall/index.tsx @@ -56,8 +56,6 @@ describe('OutdialCall Component', () => { expect(useOutdialCallSpy).toHaveBeenCalled(); // When address book is disabled, there should be no tablist expect(container.querySelector('mdc-tablist')).not.toBeInTheDocument(); - // The container should not have the additional height class - expect(container.querySelector('.height-28-5rem')).not.toBeInTheDocument(); }); it('passes isAddressBookEnabled prop correctly when set to true', () => { @@ -73,8 +71,6 @@ describe('OutdialCall Component', () => { expect(useOutdialCallSpy).toHaveBeenCalled(); // When address book is enabled, there should be a tablist expect(container.querySelector('mdc-tablist')).toBeInTheDocument(); - // The container should have the additional height class - expect(container.querySelector('.height-28-5rem')).toBeInTheDocument(); }); it('enables address book by default when isAddressBookEnabled prop is not provided', () => { @@ -90,8 +86,6 @@ describe('OutdialCall Component', () => { expect(useOutdialCallSpy).toHaveBeenCalled(); // When no prop is provided, address book should be enabled by default expect(container.querySelector('mdc-tablist')).toBeInTheDocument(); - // The container should have the additional height class - expect(container.querySelector('.height-28-5rem')).toBeInTheDocument(); }); describe('ErrorBoundary Tests', () => { From b0f7ba62c12adbf942b915aafd06090bcc8dced5 Mon Sep 17 00:00:00 2001 From: molker Date: Wed, 14 Jan 2026 10:12:19 -0500 Subject: [PATCH 2/3] Add labels --- .../components/task/OutdialCall/constants.ts | 2 ++ .../task/OutdialCall/outdial-call.style.scss | 15 +++++++++++-- .../task/OutdialCall/outdial-call.tsx | 2 ++ .../out-dial-call.snapshot.tsx.snap | 22 +++++++++++++++++++ 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/contact-center/cc-components/src/components/task/OutdialCall/constants.ts b/packages/contact-center/cc-components/src/components/task/OutdialCall/constants.ts index 71bfd35b2..5d65167cf 100644 --- a/packages/contact-center/cc-components/src/components/task/OutdialCall/constants.ts +++ b/packages/contact-center/cc-components/src/components/task/OutdialCall/constants.ts @@ -8,6 +8,8 @@ export const OutdialStrings = { OUTDIAL_CALL: 'Outdial Call', ADDRESS_BOOK_SEARCH_PLACEHOLDER: 'Search by Name/Number', ADDRESS_BOOK_NO_RESULTS: 'No address book entries found.', + TAB_ADDRESS_BOOK: 'Address Book', + TAB_DIALPAD: 'Dialpad', }; // Utility Constants diff --git a/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.style.scss b/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.style.scss index 3d0183824..cd2ae9a6b 100644 --- a/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.style.scss +++ b/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.style.scss @@ -123,14 +123,25 @@ .tab-list { padding-bottom: 1rem; + + .container { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + flex: 2; + } + mdc-button { display: none; } mdc-tab { - width: 7.395rem; - padding: 0; + --mdc-tab-glass-active-color: #000000F2; + --mdc-tab-glass-active-background-color-hover: #b2b2b2; + --mdc-tab-glass-active-background-color-normal: #DEDEDE; } + } } diff --git a/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.tsx b/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.tsx index 87ca2f742..248a61932 100644 --- a/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.tsx +++ b/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.tsx @@ -296,6 +296,7 @@ const OutdialCallComponent: React.FunctionComponent = aria-controls={TABS.ADDRESS_BOOK} variant="glass" onClick={handleAddressBookTabClick} + text={OutdialStrings.TAB_ADDRESS_BOOK} > = aria-controls={TABS.DIAL_PAD} variant="glass" onClick={handleDialpadTabClick} + text={OutdialStrings.TAB_DIALPAD} > diff --git a/packages/contact-center/cc-components/tests/components/task/OutdialCall/__snapshots__/out-dial-call.snapshot.tsx.snap b/packages/contact-center/cc-components/tests/components/task/OutdialCall/__snapshots__/out-dial-call.snapshot.tsx.snap index 2b8dc124c..664b64572 100644 --- a/packages/contact-center/cc-components/tests/components/task/OutdialCall/__snapshots__/out-dial-call.snapshot.tsx.snap +++ b/packages/contact-center/cc-components/tests/components/task/OutdialCall/__snapshots__/out-dial-call.snapshot.tsx.snap @@ -18,6 +18,7 @@ exports[`Outdial Call Component Address Book functionality displays address book role="tab" tab-id="address_book" tabindex="0" + text="Address Book" variant="glass" /> @@ -219,6 +221,7 @@ exports[`Outdial Call Component Address Book functionality renders address book role="tab" tab-id="address_book" tabindex="0" + text="Address Book" variant="glass" /> @@ -420,6 +424,7 @@ exports[`Outdial Call Component Address Book functionality renders address book role="tab" tab-id="address_book" tabindex="0" + text="Address Book" variant="glass" /> @@ -1038,6 +1044,7 @@ exports[`Outdial Call Component Address Book functionality renders address book role="tab" tab-id="address_book" tabindex="0" + text="Address Book" variant="glass" /> @@ -1383,6 +1391,7 @@ exports[`Outdial Call Component Address Book functionality renders loading spinn role="tab" tab-id="address_book" tabindex="0" + text="Address Book" variant="glass" /> @@ -2009,6 +2019,7 @@ exports[`Outdial Call Component Address Book functionality renders with address role="tab" tab-id="address_book" tabindex="-1" + text="Address Book" variant="glass" /> @@ -2339,6 +2351,7 @@ exports[`Outdial Call Component Address Book functionality renders with address role="tab" tab-id="address_book" tabindex="-1" + text="Address Book" variant="glass" /> @@ -2660,6 +2674,7 @@ exports[`Outdial Call Component Address Book functionality selects an address bo role="tab" tab-id="address_book" tabindex="0" + text="Address Book" variant="glass" /> @@ -2861,6 +2877,7 @@ exports[`Outdial Call Component Address Book functionality shows empty state whe role="tab" tab-id="address_book" tabindex="0" + text="Address Book" variant="glass" /> @@ -3028,6 +3046,7 @@ exports[`Outdial Call Component Address Book functionality switches back to dial role="tab" tab-id="address_book" tabindex="-1" + text="Address Book" variant="glass" /> @@ -3359,6 +3379,7 @@ exports[`Outdial Call Component Address Book functionality switches to address b role="tab" tab-id="address_book" tabindex="0" + text="Address Book" variant="glass" /> From e5beb9298dc0d4564479be19b91bd7e5623c6e2f Mon Sep 17 00:00:00 2001 From: Matthew Olker Date: Thu, 9 Apr 2026 10:27:21 -0400 Subject: [PATCH 3/3] Create CampaignErrorDialog Component --- .../CampaignErrorDialog.tsx | 64 +++++++ .../campaign-error-dialog.style.scss | 37 ++++ .../campaign-error-dialog.types.ts | 16 ++ .../contact-center/cc-components/src/index.ts | 3 + .../CampaignErrorDialog.tsx | 166 ++++++++++++++++++ 5 files changed, 286 insertions(+) create mode 100644 packages/contact-center/cc-components/src/components/task/CampaignErrorDialog/CampaignErrorDialog.tsx create mode 100644 packages/contact-center/cc-components/src/components/task/CampaignErrorDialog/campaign-error-dialog.style.scss create mode 100644 packages/contact-center/cc-components/src/components/task/CampaignErrorDialog/campaign-error-dialog.types.ts create mode 100644 packages/contact-center/cc-components/tests/components/task/CampaignErrorDialog/CampaignErrorDialog.tsx diff --git a/packages/contact-center/cc-components/src/components/task/CampaignErrorDialog/CampaignErrorDialog.tsx b/packages/contact-center/cc-components/src/components/task/CampaignErrorDialog/CampaignErrorDialog.tsx new file mode 100644 index 000000000..45e7e7012 --- /dev/null +++ b/packages/contact-center/cc-components/src/components/task/CampaignErrorDialog/CampaignErrorDialog.tsx @@ -0,0 +1,64 @@ +import React, {useEffect, useRef} from 'react'; +import {Button, Text} from '@momentum-design/components/dist/react'; +import {CampaignErrorDialogProps, ERROR_TITLES, ERROR_MESSAGE} from './campaign-error-dialog.types'; +import {withMetrics} from '@webex/cc-ui-logging'; +import './campaign-error-dialog.style.scss'; + +const CampaignErrorDialog: React.FunctionComponent = ({errorType, isOpen, onClose}) => { + const dialogRef = useRef(null); + + useEffect(() => { + const dialog = dialogRef.current; + if (!dialog) return; + + if (isOpen && !dialog.open) { + dialog.showModal(); + } else if (!isOpen && dialog.open) { + dialog.close(); + } + }, [isOpen]); + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Escape') { + onClose(); + } + }; + + const handleClose = () => { + onClose(); + }; + + return ( + + + {ERROR_TITLES[errorType]} + + + {ERROR_MESSAGE} + +
    + +
    +
    + ); +}; + +const CampaignErrorDialogWithMetrics = withMetrics(CampaignErrorDialog, 'CampaignErrorDialog'); +export default CampaignErrorDialogWithMetrics; diff --git a/packages/contact-center/cc-components/src/components/task/CampaignErrorDialog/campaign-error-dialog.style.scss b/packages/contact-center/cc-components/src/components/task/CampaignErrorDialog/campaign-error-dialog.style.scss new file mode 100644 index 000000000..3506d5752 --- /dev/null +++ b/packages/contact-center/cc-components/src/components/task/CampaignErrorDialog/campaign-error-dialog.style.scss @@ -0,0 +1,37 @@ +.campaign-error-dialog { + width: 25rem; + border-radius: 0.5rem; + border-color: transparent; + padding: 1rem; + box-shadow: 0rem 0.25rem 0.5rem 0rem rgba(0, 0, 0, 0.16), 0rem 0rem 0.0625rem 0rem rgba(0, 0, 0, 0.16); + + &::backdrop { + background-color: rgba(0, 0, 0, 0.5); + } + + .campaign-error-dialog-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 1rem; + } + + .campaign-error-dialog-title { + margin: 0; + flex: 1; + } + + .campaign-error-dialog-close { + margin-left: 0.5rem; + } + + .campaign-error-dialog-message { + margin-bottom: 1.5rem; + } + + .campaign-error-dialog-actions { + display: flex; + justify-content: flex-end; + gap: 0.5rem; + } +} diff --git a/packages/contact-center/cc-components/src/components/task/CampaignErrorDialog/campaign-error-dialog.types.ts b/packages/contact-center/cc-components/src/components/task/CampaignErrorDialog/campaign-error-dialog.types.ts new file mode 100644 index 000000000..78f35bbbd --- /dev/null +++ b/packages/contact-center/cc-components/src/components/task/CampaignErrorDialog/campaign-error-dialog.types.ts @@ -0,0 +1,16 @@ +export type CampaignErrorType = 'ACCEPT_FAILED' | 'SKIP_FAILED' | 'REMOVE_FAILED'; + +export interface CampaignErrorDialogProps { + errorType: CampaignErrorType; + isOpen: boolean; + onClose: () => void; +} + +export const ERROR_TITLES: Record = { + ACCEPT_FAILED: "Can't accept contact", + SKIP_FAILED: "Can't skip contact", + REMOVE_FAILED: "Can't remove contact", +}; + +export const ERROR_MESSAGE = + 'We ran into an issue connecting you with this contact. Check your network connection and try again.'; diff --git a/packages/contact-center/cc-components/src/index.ts b/packages/contact-center/cc-components/src/index.ts index d4d692fdb..64096a129 100644 --- a/packages/contact-center/cc-components/src/index.ts +++ b/packages/contact-center/cc-components/src/index.ts @@ -5,6 +5,7 @@ import CallControlCADComponent from './components/task/CallControlCAD/call-contr import IncomingTaskComponent from './components/task/IncomingTask/incoming-task'; import TaskListComponent from './components/task/TaskList/task-list'; import OutdialCallComponent from './components/task/OutdialCall/outdial-call'; +import CampaignErrorDialogComponent from './components/task/CampaignErrorDialog/CampaignErrorDialog'; export { UserStateComponent, @@ -14,8 +15,10 @@ export { IncomingTaskComponent, TaskListComponent, OutdialCallComponent, + CampaignErrorDialogComponent, }; export * from './components/StationLogin/constants'; export * from './components/StationLogin/station-login.types'; export * from './components/UserState/user-state.types'; export * from './components/task/task.types'; +export * from './components/task/CampaignErrorDialog/campaign-error-dialog.types'; diff --git a/packages/contact-center/cc-components/tests/components/task/CampaignErrorDialog/CampaignErrorDialog.tsx b/packages/contact-center/cc-components/tests/components/task/CampaignErrorDialog/CampaignErrorDialog.tsx new file mode 100644 index 000000000..f8920e10f --- /dev/null +++ b/packages/contact-center/cc-components/tests/components/task/CampaignErrorDialog/CampaignErrorDialog.tsx @@ -0,0 +1,166 @@ +import React from 'react'; +import {render, fireEvent, screen} from '@testing-library/react'; +import '@testing-library/jest-dom'; +import CampaignErrorDialogComponent from '../../../../src/components/task/CampaignErrorDialog/CampaignErrorDialog'; +import { + CampaignErrorDialogProps, + CampaignErrorType, + ERROR_TITLES, + ERROR_MESSAGE, +} from '../../../../src/components/task/CampaignErrorDialog/campaign-error-dialog.types'; + +// Mock HTMLDialogElement methods +HTMLDialogElement.prototype.showModal = jest.fn(); +HTMLDialogElement.prototype.close = jest.fn(); + +describe('CampaignErrorDialogComponent', () => { + const mockOnClose = jest.fn(); + + const defaultProps: CampaignErrorDialogProps = { + errorType: 'ACCEPT_FAILED', + isOpen: false, + onClose: mockOnClose, + }; + + beforeEach(() => { + jest.clearAllMocks(); + (HTMLDialogElement.prototype.showModal as jest.Mock).mockClear(); + (HTMLDialogElement.prototype.close as jest.Mock).mockClear(); + }); + + describe('Rendering', () => { + it('should render the dialog element', () => { + render(); + + const dialog = screen.getByTestId('campaign-error-dialog'); + expect(dialog).toBeInTheDocument(); + expect(dialog).toHaveClass('campaign-error-dialog'); + }); + + it('should render the correct title for ACCEPT_FAILED error type', () => { + render(); + + const title = screen.getByTestId('campaign-error-dialog-title'); + expect(title).toHaveTextContent(ERROR_TITLES.ACCEPT_FAILED); + expect(title).toHaveTextContent("Can't accept contact"); + }); + + it('should render the correct title for SKIP_FAILED error type', () => { + render(); + + const title = screen.getByTestId('campaign-error-dialog-title'); + expect(title).toHaveTextContent(ERROR_TITLES.SKIP_FAILED); + expect(title).toHaveTextContent("Can't skip contact"); + }); + + it('should render the correct title for REMOVE_FAILED error type', () => { + render(); + + const title = screen.getByTestId('campaign-error-dialog-title'); + expect(title).toHaveTextContent(ERROR_TITLES.REMOVE_FAILED); + expect(title).toHaveTextContent("Can't remove contact"); + }); + + it('should render the error message', () => { + render(); + + const message = screen.getByTestId('campaign-error-dialog-message'); + expect(message).toHaveTextContent(ERROR_MESSAGE); + expect(message).toHaveTextContent( + 'We ran into an issue connecting you with this contact. Check your network connection and try again.' + ); + }); + + it('should render the OK button', () => { + render(); + + const okButton = screen.getByTestId('campaign-error-dialog-ok-button'); + expect(okButton).toBeInTheDocument(); + expect(okButton).toHaveTextContent('OK'); + }); + }); + + describe('Dialog Open/Close Behavior', () => { + it('should call showModal when isOpen changes to true', () => { + const {rerender} = render(); + + expect(HTMLDialogElement.prototype.showModal).not.toHaveBeenCalled(); + + rerender(); + + expect(HTMLDialogElement.prototype.showModal).toHaveBeenCalledTimes(1); + }); + + it('should call close when isOpen changes to false', () => { + const {rerender} = render(); + + // Simulate dialog being open + const dialog = screen.getByTestId('campaign-error-dialog') as HTMLDialogElement; + Object.defineProperty(dialog, 'open', {value: true, writable: true}); + + rerender(); + + expect(HTMLDialogElement.prototype.close).toHaveBeenCalledTimes(1); + }); + }); + + describe('User Interactions', () => { + it('should call onClose when OK button is clicked', () => { + render(); + + const okButton = screen.getByTestId('campaign-error-dialog-ok-button'); + fireEvent.click(okButton); + + expect(mockOnClose).toHaveBeenCalledTimes(1); + }); + + it('should call onClose when Escape key is pressed', () => { + render(); + + const dialog = screen.getByTestId('campaign-error-dialog'); + fireEvent.keyDown(dialog, {key: 'Escape'}); + + expect(mockOnClose).toHaveBeenCalledTimes(1); + }); + + it('should not call onClose when other keys are pressed', () => { + render(); + + const dialog = screen.getByTestId('campaign-error-dialog'); + fireEvent.keyDown(dialog, {key: 'Enter'}); + fireEvent.keyDown(dialog, {key: 'Tab'}); + fireEvent.keyDown(dialog, {key: 'Space'}); + + expect(mockOnClose).not.toHaveBeenCalled(); + }); + }); + + describe('Error Type Mapping', () => { + const errorTypes: CampaignErrorType[] = ['ACCEPT_FAILED', 'SKIP_FAILED', 'REMOVE_FAILED']; + + errorTypes.forEach((errorType) => { + it(`should display correct title for ${errorType}`, () => { + render(); + + const title = screen.getByTestId('campaign-error-dialog-title'); + expect(title).toHaveTextContent(ERROR_TITLES[errorType]); + }); + }); + }); + + describe('Accessibility', () => { + it('should have proper dialog structure', () => { + render(); + + const dialog = screen.getByTestId('campaign-error-dialog'); + expect(dialog.tagName).toBe('DIALOG'); + }); + + it('should have heading element for title', () => { + render(); + + const title = screen.getByTestId('campaign-error-dialog-title'); + expect(title).toBeInTheDocument(); + }); + }); +});