Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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 @@
export const E911ModalLabels = {
TITLE: 'Emergency Service Notification',
WARNING_TITLE: 'Important',
WARNING_MESSAGE:
'If your address has changed, update it in the Emergency Callback Number (ECBN) section of your profile before making any calls.',
HELP_LINK_TEXT: 'See help section',
DIALING_TITLE: 'Dialing Emergency Services',
DIALING_MESSAGE:
'When you dial an emergency number (e.g., 911), your call will be routed to the appropriate emergency services based on your registered address. Ensure your address is always up to date to receive timely assistance.',
CHECKBOX_LABEL: 'I have read the notification',
CANCEL: 'Cancel',
SAVE_AND_CONTINUE: 'Save & Continue',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
.e911-modal {
padding: 0;
border: none;
border-radius: 8px;
Comment thread
brain-frog marked this conversation as resolved.
Outdated
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
max-width: 480px;
width: 90%;

&::backdrop {
background-color: rgba(0, 0, 0, 0.5);
}

.e911-modal-content {
padding: 24px;
}

.e911-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;

.e911-modal-title {
margin: 0;
font-size: 18px;
font-weight: 600;
}

.e911-close-button {
background: none;
border: none;
cursor: pointer;
padding: 4px;
}
}

.e911-warning-box {
background-color: #fff3cd;
border: 1px solid #ffc107;
border-radius: 4px;
padding: 12px;
margin-bottom: 16px;

.e911-warning-title {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
font-weight: 600;
color: #856404;
}

.e911-warning-message {
color: #856404;
font-size: 14px;
line-height: 1.5;
}

.e911-help-link {
color: #0056b3;
text-decoration: underline;
cursor: pointer;
font-size: 14px;
margin-top: 8px;
display: inline-block;
}
}

.e911-dialing-section {
margin-bottom: 20px;

.e911-dialing-title {
font-weight: 600;
margin-bottom: 8px;
}

.e911-dialing-message {
font-size: 14px;
line-height: 1.5;
color: #666;
}
}

.e911-checkbox-container {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 20px;

.e911-checkbox-label {
font-size: 14px;
cursor: pointer;
}
}

.e911-modal-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
padding-top: 16px;
border-top: 1px solid #e0e0e0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React, {useRef, useEffect, useState} from 'react';
import {Button, Text, Icon, Checkbox} from '@momentum-design/components/dist/react';
import {E911ModalProps} from './e911-modal.types';
import {E911ModalLabels} from './e911-modal.constants';
import './e911-modal.style.scss';

const E911Modal: React.FC<E911ModalProps> = ({isOpen, onSaveAndContinue, onCancel}) => {
const dialogRef = useRef<HTMLDialogElement>(null);
const [isChecked, setIsChecked] = useState(false);

useEffect(() => {
const dialog = dialogRef.current;
if (!dialog) return;

if (isOpen) {
if (!dialog.open) {
dialog.showModal();
}
} else {
if (dialog.open) {
dialog.close();
}
setIsChecked(false);
}
}, [isOpen]);

const handleCheckboxChange = () => {
setIsChecked(!isChecked);
};

const handleCancel = () => {
setIsChecked(false);
onCancel();
};

const handleSaveAndContinue = () => {
if (isChecked) {
onSaveAndContinue();
}
};

return (
<dialog ref={dialogRef} className="e911-modal" data-testid="e911-modal">
Comment thread
brain-frog marked this conversation as resolved.
Outdated
Comment thread
brain-frog marked this conversation as resolved.
Outdated
<div className="e911-modal-content">
<div className="e911-modal-header">
<Text tagname="h2" type="body-large-bold" className="e911-modal-title">
{E911ModalLabels.TITLE}
</Text>
<Button
size={32}
variant="tertiary"
color="default"
prefix-icon="cancel-bold"
type="button"
role="button"
aria-label="Close"
onClick={handleCancel}
className="e911-close-button"
data-testid="e911-close-button"
/>
</div>

<div className="e911-warning-box">
<div className="e911-warning-title">
<Icon name="warning-filled" size={1} />
<Text tagname="span" type="body-midsize-bold">
{E911ModalLabels.WARNING_TITLE}
</Text>
</div>
<Text tagname="p" type="body-midsize-regular" className="e911-warning-message">
{E911ModalLabels.WARNING_MESSAGE}
</Text>
<a href="#" className="e911-help-link" data-testid="e911-help-link">
Comment thread
brain-frog marked this conversation as resolved.
Outdated
{E911ModalLabels.HELP_LINK_TEXT}
</a>
</div>

<div className="e911-dialing-section">
<Text tagname="h3" type="body-midsize-bold" className="e911-dialing-title">
{E911ModalLabels.DIALING_TITLE}
</Text>
<Text tagname="p" type="body-midsize-regular" className="e911-dialing-message">
{E911ModalLabels.DIALING_MESSAGE}
</Text>
</div>

<div className="e911-checkbox-container">
<Checkbox
data-testid="e911-checkbox"
checked={isChecked}
onChange={handleCheckboxChange}
Comment thread
brain-frog marked this conversation as resolved.
Outdated
label={E911ModalLabels.CHECKBOX_LABEL}
/>
</div>

<div className="e911-modal-footer">
<Button variant="secondary" onClick={handleCancel} data-testid="e911-cancel-button" className="white-button">
{E911ModalLabels.CANCEL}
</Button>
<Button onClick={handleSaveAndContinue} disabled={!isChecked} data-testid="e911-save-button">
{E911ModalLabels.SAVE_AND_CONTINUE}
</Button>
</div>
</div>
</dialog>
);
};

export default E911Modal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface E911ModalProps {
isOpen: boolean;
onSaveAndContinue: () => void;
onCancel: () => void;
}
3 changes: 3 additions & 0 deletions packages/contact-center/cc-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import CampaignErrorDialogComponent from './components/task/CampaignErrorDialog/
import CampaignCountdownComponent from './components/task/CampaignCountdown/campaign-countdown';
import CampaignTaskComponent from './components/task/CampaignTask/campaign-task';
import RealTimeTranscriptComponent from './components/task/RealTimeTranscript/real-time-transcript';
import E911Modal from './components/StationLogin/E911Modal/e911-modal';

export {
UserStateComponent,
Expand All @@ -22,8 +23,10 @@ export {
CampaignCountdownComponent,
CampaignTaskComponent,
RealTimeTranscriptComponent,
E911Modal,
Comment thread
brain-frog marked this conversation as resolved.
};
export * from './components/StationLogin/constants';
export * from './components/StationLogin/E911Modal/e911-modal.types';
export * from './components/StationLogin/station-login.types';
export * from './components/UserState/user-state.types';
export * from './components/task/task.types';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import React from 'react';
import {render, screen, fireEvent, waitFor} from '@testing-library/react';
import '@testing-library/jest-dom';
import E911Modal from '../../../../src/components/StationLogin/E911Modal/e911-modal';
import {E911ModalLabels} from '../../../../src/components/StationLogin/E911Modal/e911-modal.constants';

jest.mock('@webex/cc-ui-logging', () => ({
withMetrics: (component: React.ComponentType<Record<string, unknown>>) => component,
}));

describe('E911Modal', () => {
const defaultProps = {
isOpen: true,
onSaveAndContinue: jest.fn(),
onCancel: jest.fn(),
};

beforeEach(() => {
jest.clearAllMocks();
HTMLDialogElement.prototype.showModal = jest.fn();
HTMLDialogElement.prototype.close = jest.fn();
});

it('should render the modal when isOpen is true', () => {
render(<E911Modal {...defaultProps} />);
expect(screen.getByTestId('e911-modal')).toBeInTheDocument();
});

it('should display the modal title', () => {
render(<E911Modal {...defaultProps} />);
expect(screen.getByText(E911ModalLabels.TITLE)).toBeInTheDocument();
});

it('should display the warning message', () => {
render(<E911Modal {...defaultProps} />);
expect(screen.getByText(E911ModalLabels.WARNING_MESSAGE)).toBeInTheDocument();
});

it('should display the dialing section', () => {
render(<E911Modal {...defaultProps} />);
expect(screen.getByText(E911ModalLabels.DIALING_TITLE)).toBeInTheDocument();
expect(screen.getByText(E911ModalLabels.DIALING_MESSAGE)).toBeInTheDocument();
});

it('should display the checkbox with label', () => {
render(<E911Modal {...defaultProps} />);
expect(screen.getByTestId('e911-checkbox')).toBeInTheDocument();
});

it('should not call onSaveAndContinue when button clicked without checkbox checked', () => {
render(<E911Modal {...defaultProps} />);
const saveButton = screen.getByTestId('e911-save-button');

fireEvent.click(saveButton);

// Button click should not trigger callback when checkbox is unchecked
expect(defaultProps.onSaveAndContinue).not.toHaveBeenCalled();
});

it('should enable Save & Continue button when checkbox is checked', async () => {
render(<E911Modal {...defaultProps} />);
const checkbox = screen.getByTestId('e911-checkbox');

fireEvent(checkbox, new CustomEvent('change', {detail: {checked: true}}));

await waitFor(() => {
const saveButton = screen.getByTestId('e911-save-button');
expect(saveButton).not.toBeDisabled();
});
});

it('should call onCancel when Cancel button is clicked', () => {
render(<E911Modal {...defaultProps} />);
const cancelButton = screen.getByTestId('e911-cancel-button');

fireEvent.click(cancelButton);

expect(defaultProps.onCancel).toHaveBeenCalledTimes(1);
});

it('should call onCancel when close button is clicked', () => {
render(<E911Modal {...defaultProps} />);
const closeButton = screen.getByTestId('e911-close-button');

fireEvent.click(closeButton);

expect(defaultProps.onCancel).toHaveBeenCalledTimes(1);
});

it('should call onSaveAndContinue when Save & Continue is clicked with checkbox checked', async () => {
render(<E911Modal {...defaultProps} />);
const checkbox = screen.getByTestId('e911-checkbox');

fireEvent(checkbox, new CustomEvent('change', {detail: {checked: true}}));

await waitFor(() => {
const saveButton = screen.getByTestId('e911-save-button');
expect(saveButton).not.toBeDisabled();
});

const saveButton = screen.getByTestId('e911-save-button');
fireEvent.click(saveButton);

expect(defaultProps.onSaveAndContinue).toHaveBeenCalledTimes(1);
});

it('should call showModal when isOpen changes to true', () => {
const {rerender} = render(<E911Modal {...defaultProps} isOpen={false} />);

rerender(<E911Modal {...defaultProps} isOpen={true} />);

expect(HTMLDialogElement.prototype.showModal).toHaveBeenCalled();
});

it('should reset checkbox state when modal closes', () => {
const {rerender} = render(<E911Modal {...defaultProps} isOpen={true} />);

rerender(<E911Modal {...defaultProps} isOpen={false} />);

// Modal should attempt to close - the actual close behavior depends on dialog.open state
expect(screen.getByTestId('e911-modal')).toBeInTheDocument();
});

it('should display help link', () => {
render(<E911Modal {...defaultProps} />);
expect(screen.getByTestId('e911-help-link')).toBeInTheDocument();
expect(screen.getByText(E911ModalLabels.HELP_LINK_TEXT)).toBeInTheDocument();
});
});
Loading