-
Notifications
You must be signed in to change notification settings - Fork 65
feat(task): Create CampaginErrorDialog Component #670
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
brain-frog
wants to merge
9
commits into
webex:next
Choose a base branch
from
brain-frog:cai-7665
base: next
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
7d8e322
Update OutdialCall CSS
73d6e8b
Merge branch 'next' into next
brain-frog b0f7ba6
Add labels
6d39f62
Merge branch 'next' of github.com:brain-frog/widgets into next
e0773f6
Merge branch 'webex:next' into next
brain-frog ab7169f
Merge branch 'next' of github.com:brain-frog/widgets into next
aa08727
Merge branch 'next' of github.com:brain-frog/widgets into next
e5beb92
Create CampaignErrorDialog Component
04717f3
Merge branch 'next' into cai-7665
brain-frog File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
64 changes: 64 additions & 0 deletions
64
...tact-center/cc-components/src/components/task/CampaignErrorDialog/CampaignErrorDialog.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<CampaignErrorDialogProps> = ({errorType, isOpen, onClose}) => { | ||
| const dialogRef = useRef<HTMLDialogElement>(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<HTMLDialogElement>) => { | ||
| if (event.key === 'Escape') { | ||
| onClose(); | ||
| } | ||
| }; | ||
|
|
||
| const handleClose = () => { | ||
| onClose(); | ||
| }; | ||
|
|
||
| return ( | ||
| <dialog | ||
| ref={dialogRef} | ||
| className="campaign-error-dialog" | ||
| data-testid="campaign-error-dialog" | ||
| onKeyDown={handleKeyDown} | ||
| > | ||
| <Text | ||
| tagname="h2" | ||
| type="body-large-bold" | ||
| className="campaign-error-dialog-title" | ||
| data-testid="campaign-error-dialog-title" | ||
| > | ||
| {ERROR_TITLES[errorType]} | ||
| </Text> | ||
| <Text | ||
| tagname="p" | ||
| type="body-midsize-regular" | ||
| className="campaign-error-dialog-message" | ||
| data-testid="campaign-error-dialog-message" | ||
| > | ||
| {ERROR_MESSAGE} | ||
| </Text> | ||
| <div className="campaign-error-dialog-actions"> | ||
| <Button onClick={handleClose} data-testid="campaign-error-dialog-ok-button"> | ||
| OK | ||
| </Button> | ||
| </div> | ||
| </dialog> | ||
| ); | ||
| }; | ||
|
|
||
| const CampaignErrorDialogWithMetrics = withMetrics(CampaignErrorDialog, 'CampaignErrorDialog'); | ||
| export default CampaignErrorDialogWithMetrics; | ||
37 changes: 37 additions & 0 deletions
37
...er/cc-components/src/components/task/CampaignErrorDialog/campaign-error-dialog.style.scss
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
| } | ||
| } |
16 changes: 16 additions & 0 deletions
16
...nter/cc-components/src/components/task/CampaignErrorDialog/campaign-error-dialog.types.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<CampaignErrorType, string> = { | ||
| 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.'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
166 changes: 166 additions & 0 deletions
166
...ct-center/cc-components/tests/components/task/CampaignErrorDialog/CampaignErrorDialog.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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(<CampaignErrorDialogComponent {...defaultProps} />); | ||
|
|
||
| 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(<CampaignErrorDialogComponent {...defaultProps} errorType="ACCEPT_FAILED" isOpen={true} />); | ||
|
|
||
| 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(<CampaignErrorDialogComponent {...defaultProps} errorType="SKIP_FAILED" isOpen={true} />); | ||
|
|
||
| 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(<CampaignErrorDialogComponent {...defaultProps} errorType="REMOVE_FAILED" isOpen={true} />); | ||
|
|
||
| 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(<CampaignErrorDialogComponent {...defaultProps} isOpen={true} />); | ||
|
|
||
| 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(<CampaignErrorDialogComponent {...defaultProps} isOpen={true} />); | ||
|
|
||
| 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(<CampaignErrorDialogComponent {...defaultProps} isOpen={false} />); | ||
|
|
||
| expect(HTMLDialogElement.prototype.showModal).not.toHaveBeenCalled(); | ||
|
|
||
| rerender(<CampaignErrorDialogComponent {...defaultProps} isOpen={true} />); | ||
|
|
||
| expect(HTMLDialogElement.prototype.showModal).toHaveBeenCalledTimes(1); | ||
| }); | ||
|
|
||
| it('should call close when isOpen changes to false', () => { | ||
| const {rerender} = render(<CampaignErrorDialogComponent {...defaultProps} isOpen={true} />); | ||
|
|
||
| // Simulate dialog being open | ||
| const dialog = screen.getByTestId('campaign-error-dialog') as HTMLDialogElement; | ||
| Object.defineProperty(dialog, 'open', {value: true, writable: true}); | ||
|
|
||
| rerender(<CampaignErrorDialogComponent {...defaultProps} isOpen={false} />); | ||
|
|
||
| expect(HTMLDialogElement.prototype.close).toHaveBeenCalledTimes(1); | ||
| }); | ||
| }); | ||
|
|
||
| describe('User Interactions', () => { | ||
| it('should call onClose when OK button is clicked', () => { | ||
| render(<CampaignErrorDialogComponent {...defaultProps} isOpen={true} />); | ||
|
|
||
| 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(<CampaignErrorDialogComponent {...defaultProps} isOpen={true} />); | ||
|
|
||
| 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(<CampaignErrorDialogComponent {...defaultProps} isOpen={true} />); | ||
|
|
||
| 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(<CampaignErrorDialogComponent {...defaultProps} errorType={errorType} isOpen={true} />); | ||
|
|
||
| const title = screen.getByTestId('campaign-error-dialog-title'); | ||
| expect(title).toHaveTextContent(ERROR_TITLES[errorType]); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| describe('Accessibility', () => { | ||
| it('should have proper dialog structure', () => { | ||
| render(<CampaignErrorDialogComponent {...defaultProps} isOpen={true} />); | ||
|
|
||
| const dialog = screen.getByTestId('campaign-error-dialog'); | ||
| expect(dialog.tagName).toBe('DIALOG'); | ||
| }); | ||
|
|
||
| it('should have heading element for title', () => { | ||
| render(<CampaignErrorDialogComponent {...defaultProps} isOpen={true} />); | ||
|
|
||
| const title = screen.getByTestId('campaign-error-dialog-title'); | ||
| expect(title).toBeInTheDocument(); | ||
| }); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doesnt momentum have a dialog you can use? and can you make a generic dialog that accepts props and then use that here? That way if we need another one we don't have to do this again
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does! However the momentum-design package has not been updated in 11 months and the changes necessary to use the newer version are large enough that they should be their own story.
I'd be happy to take that story as well.