diff --git a/source/frontend/src/assets/scss/App.scss b/source/frontend/src/assets/scss/App.scss index 38427edf2e..36cd16ad38 100644 --- a/source/frontend/src/assets/scss/App.scss +++ b/source/frontend/src/assets/scss/App.scss @@ -1,114 +1,5 @@ @import './styles.scss'; -.modal-dialog { - div.modal-header { - height: 4.8rem; - padding: 0 1.6rem; - display: flex; - flex-direction: row; - align-items: center; - color: $primary-background-color; - background-color: $primary-color; - - .modal-title { - font-family: BcSans-Bold; - font-size: 2.2rem; - height: 2.75rem; - height: auto; - } - - .header-icon { - margin-right: 8px; - display: inline-block; - } - - .modal-close-btn { - cursor: pointer; - } - } - - div.modal-body { - padding: 2.4rem 1.8rem; - font-size: 1.8rem; - } - - div.modal-footer { - border-top: none; - - hr { - width: 100%; - } - - .button-wrap { - display: inline-flex; - margin-top: 2.4rem; - margin-bottom: 2.4rem; - } - - button { - margin-right: 2.4rem; - min-width: 9.5rem; - height: 3.9rem; - } - } - - .close { - color: black; - } - - &.modal-xl { - max-width: 100rem; - } - - &.modal-l { - max-width: 75rem; - } - - &.modal-m { - max-width: 50rem; - } - - &.modal-s { - max-width: 40rem; - } - - &.info { - .modal-header { - color: $dark-blue; - background-color: $filter-box-color; - } - - .modal-close-btn { - color: $text-color; - cursor: pointer; - } - } - - &.error { - .modal-header { - color: $font-danger-color; - background-color: $danger-background-color; - } - - .modal-close-btn { - color: $text-color; - cursor: pointer; - } - } - - &.warning { - .modal-header { - color: $font-warning-color; - background-color: $summary-color; - } - - .modal-close-btn { - color: $text-color; - cursor: pointer; - } - } -} - .Toastify__toast .Toastify__toast-body { word-break: break-word; } diff --git a/source/frontend/src/assets/scss/_variables.module.scss b/source/frontend/src/assets/scss/_variables.module.scss index 3db8bdbbf9..c4fa65104e 100644 --- a/source/frontend/src/assets/scss/_variables.module.scss +++ b/source/frontend/src/assets/scss/_variables.module.scss @@ -6,40 +6,6 @@ footerHeight: $footer-height; headerHeight: $header-height; - primaryColor: $primary-color; - primaryLightColor: $primary-light-color; - secondaryVariantColor: $secondary-variant-color; - selectedColor: $selected-color; - lightVariantColor: $light-variant-color; - darkVariantColor: $dark-variant-color; - textColor: $text-color; - formTextColor: $form-text-color; - formControlTextColor: $form-control-text-color; - formBackgroundColor: $form-background-color; - primaryBackgroundColor: $primary-background-color; - dropdownBackgroundColor: $dropdown-background-color; - accentColor: $accent-color; - lightAccentColor: $light-accent-color; - sresIconColor: $sres-icon-color; - dangerColor: $danger-color; - lightDangerColor: $light-danger-color; - iconLightColor: $icon-light-color; - activeColor: $active-color; - completedColor: $completed-color; - filterBackgroundColor: $filter-background-color; - slideOutBlue: $slide-out-blue; - disabledColor: $disabled-color; - disabledFieldBackgroundColor: $disabled-field-background-color; - filterBoxColor: $filter-box-color; - draftColor: $draft-color; - linkColor: $link-color; - linkHoverColor: $link-hover-color; - subtleColor: $subtle-color; - discardedColor: $discarded-color; - dangerBackgroundColor: $danger-background-color; - summaryColor: $summary-color; - summaryBorderColor: $summary-border-color; - primaryBorderColor: $primary-border-color; // table ui tableHoverColor: $table-hover-color; tableHeaderBgColor: $table-header-bg-color; @@ -55,4 +21,43 @@ // buttons buttonOutlineColor: $icon-light-color; buttonInfoColor: $button-info-color; + + // Colors + accentColor: $accent-color; + activeColor: $active-color; + completedColor: $completed-color; + dangerBackgroundColor: $danger-background-color; + dangerColor: $danger-color; + darkBlue: $dark-blue; + darkVariantColor: $dark-variant-color; + disabledColor: $disabled-color; + disabledFieldBackgroundColor: $disabled-field-background-color; + discardedColor: $discarded-color; + draftColor: $draft-color; + dropdownBackgroundColor: $dropdown-background-color; + filterBackgroundColor: $filter-background-color; + filterBoxColor: $filter-box-color; + fontDangerColor: $font-danger-color; + fontWarningColor: $font-warning-color; + formBackgroundColor: $form-background-color; + formControlTextColor: $form-control-text-color; + formTextColor: $form-text-color; + iconLightColor: $icon-light-color; + lightAccentColor: $light-accent-color; + lightDangerColor: $light-danger-color; + lightVariantColor: $light-variant-color; + linkColor: $link-color; + linkHoverColor: $link-hover-color; + primaryBackgroundColor: $primary-background-color; + primaryBorderColor: $primary-border-color; + primaryColor: $primary-color; + primaryLightColor: $primary-light-color; + secondaryVariantColor: $secondary-variant-color; + selectedColor: $selected-color; + slideOutBlue: $slide-out-blue; + sresIconColor: $sres-icon-color; + subtleColor: $subtle-color; + summaryBorderColor: $summary-border-color; + summaryColor: $summary-color; + textColor: $text-color; } diff --git a/source/frontend/src/components/common/CancelConfirmationModal.tsx b/source/frontend/src/components/common/CancelConfirmationModal.tsx index 8e9fad5dff..34068cd965 100644 --- a/source/frontend/src/components/common/CancelConfirmationModal.tsx +++ b/source/frontend/src/components/common/CancelConfirmationModal.tsx @@ -2,8 +2,11 @@ import React from 'react'; import GenericModal, { ModalProps } from '@/components/common/GenericModal'; -export const CancelConfirmationModal: React.FC> = props => { +export const CancelConfirmationModal: React.FC< + React.PropsWithChildren> +> = props => { const { + variant = 'info', title = 'Unsaved Changes', message = 'You have made changes on this form. Do you wish to leave without saving?', okButtonText = 'Confirm', @@ -13,6 +16,7 @@ export const CancelConfirmationModal: React.FC { const history = useHistory(); return ( {showTabErrorModal && ( { - const { asFragment } = render(); + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); it('renders title based off of prop...', () => { - const { getByText } = render(); + const { getByText } = render(); expect(getByText('Test Title')).toBeInTheDocument(); }); it('renders message based off of prop', () => { - const { getByText } = render(); + const { getByText } = render(); expect(getByText('Test Message')).toBeInTheDocument(); }); it('renders button text based off of prop', () => { const { getByText } = render( - , + , ); expect(getByText('Ok Text')).toBeInTheDocument(); expect(getByText('Cancel Text')).toBeInTheDocument(); @@ -27,7 +27,9 @@ it('renders button text based off of prop', () => { it('calls custom ok on click', async () => { const mockOk = jest.fn(); - const { getByText } = render(); + const { getByText } = render( + , + ); const okButton = getByText('Ok Button'); await waitFor(() => { fireEvent.click(okButton); @@ -38,7 +40,7 @@ it('calls custom ok on click', async () => { it('calls custom cancel funciton on click', async () => { const mockCancel = jest.fn(); const { getByText } = render( - , + , ); const cancelButton = getByText('Cancel Button'); await waitFor(() => { diff --git a/source/frontend/src/components/common/GenericModal.tsx b/source/frontend/src/components/common/GenericModal.tsx index 01029dffef..108f19b997 100644 --- a/source/frontend/src/components/common/GenericModal.tsx +++ b/source/frontend/src/components/common/GenericModal.tsx @@ -77,6 +77,8 @@ export interface ModalContent { closeButton?: boolean; /** provide the size of the modal, default width is 50.0rem */ modalSize?: ModalSize; + variant: 'info' | 'warning' | 'error'; + //variant?: string; className?: string; /** display this modal as a popup instead of as a modal, allowing the user to click on underlying elements */ asPopup?: boolean; @@ -106,6 +108,7 @@ export const GenericModal = (props: Omit & ModalProps) = closeButton, hideFooter, modalSize, + variant, className, headerIcon, ...rest @@ -144,7 +147,7 @@ export const GenericModal = (props: Omit & ModalProps) = return <>{headerIcon}; } - switch (className) { + switch (variant) { case 'info': case 'warning': { return ; @@ -158,15 +161,36 @@ export const GenericModal = (props: Omit & ModalProps) = } }; + function getVariantClass() { + switch (variant) { + case 'info': + return 'info-variant'; + case 'warning': { + return 'warning-variant'; + } + case 'error': { + return 'error-variant'; + } + default: { + return 'info-variant'; + } + } + } + + function getModalClass() { + return (className || '') + ' ' + getVariantClass(); + } + const headerIconValue = getHeaderIcon(); return ( @@ -238,6 +262,113 @@ const StyledModal = styled(Modal)` font-family: 'Helvetica Narrow'; } } + + .modal-header { + height: 4.8rem; + padding: 0 1.6rem; + display: flex; + flex-direction: row; + align-items: center; + color: ${props => props.theme.css.primaryBackgroundColor}; + background-color: ${props => props.theme.css.primaryColor}; + + .modal-title { + font-family: BcSans-Bold; + font-size: 2.2rem; + height: 2.75rem; + height: auto; + } + + .header-icon { + margin-right: 8px; + display: inline-block; + } + + .modal-close-btn { + cursor: pointer; + } + } + + .modal-body { + padding: 2.4rem 1.8rem; + font-size: 1.8rem; + } + + .modal-footer { + border-top: none; + + hr { + width: 100%; + } + + .button-wrap { + display: inline-flex; + margin-top: 2.4rem; + margin-bottom: 2.4rem; + + button { + margin-right: 2.4rem; + min-width: 9.5rem; + height: 3.9rem; + } + } + } + + .close { + color: black; + } + + &.modal-xl { + max-width: 100rem; + } + + &.modal-l { + max-width: 75rem; + } + + &.modal-m { + max-width: 50rem; + } + + &.modal-s { + max-width: 40rem; + } + + &.info-variant { + .modal-header { + color: ${props => props.theme.css.darkBlue}; + background-color: ${props => props.theme.css.filterBoxColor}; + } + + .modal-close-btn { + color: ${props => props.theme.css.textColor}; + cursor: pointer; + } + } + + &.error-variant { + .modal-header { + color: ${props => props.theme.css.fontDangerColor}; + background-color: ${props => props.theme.css.dangerBackgroundColor}; + } + + .modal-close-btn { + color: ${props => props.theme.css.textColor}; + cursor: pointer; + } + } + + &.warning-variant { + .modal-header { + color: ${props => props.theme.css.fontWarningColor}; + background-color: ${props => props.theme.css.summaryColor}; + } + + .modal-close-btn { + color: ${props => props.theme.css.textColor}; + cursor: pointer; + } + } `; const PopupContainer = styled.div` diff --git a/source/frontend/src/components/common/ModalContainer.test.tsx b/source/frontend/src/components/common/ModalContainer.test.tsx index f104e9491c..5fd5dd193c 100644 --- a/source/frontend/src/components/common/ModalContainer.test.tsx +++ b/source/frontend/src/components/common/ModalContainer.test.tsx @@ -25,7 +25,7 @@ describe('ModalContainer component', () => { // render component under test const component = render( <> - + , { ...renderOptions, @@ -44,7 +44,7 @@ describe('ModalContainer component', () => { }); it('displays a modal based on props', async () => { - setup({ modalProps: { title: 'test', message: 'test 2' }, isVisible: true }); + setup({ modalProps: { variant: 'info', title: 'test', message: 'test 2' }, isVisible: true }); expect(await screen.findByText('test')).toBeVisible(); expect(await screen.findByText('test 2')).toBeVisible(); @@ -52,7 +52,7 @@ describe('ModalContainer component', () => { it('shows/hides modal', async () => { setup({ - modalProps: { title: 'test', message: 'test 2', okButtonText: 'ok' }, + modalProps: { variant: 'info', title: 'test', message: 'test 2', okButtonText: 'ok' }, isVisible: true, }); diff --git a/source/frontend/src/components/common/form/NotesModal.tsx b/source/frontend/src/components/common/form/NotesModal.tsx index 169c88fea7..a276eab42d 100644 --- a/source/frontend/src/components/common/form/NotesModal.tsx +++ b/source/frontend/src/components/common/form/NotesModal.tsx @@ -42,7 +42,7 @@ export const NotesModal: React.FunctionComponent { planNumberInventoryData = await loadPropertiesTask; } catch (err) { setModalContent({ - className: 'info', + variant: 'error', title: 'Unable to connect to PIMS Inventory', message: 'PIMS is unable to connect to connect to the PIMS Inventory map service. You may need to log out and log into the application in order to restore this functionality. If this error persists, contact a site administrator.', @@ -235,6 +235,7 @@ export const useMapSearch = () => { pidPinInventoryData = await loadPropertiesTask; } catch (err) { setModalContent({ + variant: 'error', title: 'Unable to connect to PIMS Inventory', message: 'PIMS is unable to connect to connect to the PIMS Inventory map service. You may need to log out and log into the application in order to restore this functionality. If this error persists, contact a site administrator.', diff --git a/source/frontend/src/components/contact/ContactManagerModal.tsx b/source/frontend/src/components/contact/ContactManagerModal.tsx index f34948d8f1..4538a2aa41 100644 --- a/source/frontend/src/components/contact/ContactManagerModal.tsx +++ b/source/frontend/src/components/contact/ContactManagerModal.tsx @@ -23,7 +23,7 @@ export const ContactManagerModal: React.FunctionComponent< > = props => { return ( { const dispatch = useDispatch(); - const handleClose = () => setShow(false); + const handleClose = () => { + setShow(false); + }; const handleClear = () => { errors.forEach(error => dispatch(logClear(error.name))); setShow(false); }; - return ( - - - Errors - + const errorUrl = (error: IGenericNetworkAction): string => { + return error?.error?.response?.config?.url || ''; + }; - - {errors.map((error: IGenericNetworkAction, index: number) => ( - - {process.env.NODE_ENV === 'development' ? ( - - - {error?.error?.response?.config?.url?.substr(0, 20)} - - : {error?.error?.response?.statusText} data:{' '} - {JSON.stringify(error?.error?.response?.data)} - - ) : ( - - - {error?.error?.response?.config?.url?.substr(0, 20)} - - : ({error?.error?.response?.statusText ?? 'unknown'}){' '} - {(error?.error?.response?.data as unknown & { error: string })?.error ?? ''} - - )} - - ))} - + const errorShortUrl = (error: IGenericNetworkAction): string => { + var url = errorUrl(error); + if (url.length > 20) { + return error?.error?.response?.config?.url?.substr(0, 20) + '...'; + } else { + return url; + } + }; - - - - + const errorStatus = (error: IGenericNetworkAction): string => { + return error?.error?.response?.statusText || 'unknown'; + }; + + return ( + + {errors.map((error: IGenericNetworkAction, index: number) => ( + + {process.env.NODE_ENV === 'development' ? ( +
+ + {errorStatus(error)} + + + {errorUrl(error)} + + + + {JSON.stringify(error?.error?.response?.data)} + + +
+ ) : ( +
+ + {errorStatus(error)} + + + {errorUrl(error)} + + + + {(error?.error?.response?.data as unknown & { error: string })?.error ?? ''} + + +
+ )} +
+ ))} + + } + /> ); }; export default ErrorModal; + +const ErrorWrapper = styled(StyledGreySection)` + max-height: 70rem; + overflow-y: scroll; + width: 100%; + padding: 0; +`; + +const ErrorEntry = styled.div``; + +const ErrorDescription = styled.div` + word-break: break-all; +`; diff --git a/source/frontend/src/components/layout/Header/__snapshots__/Header.test.tsx.snap b/source/frontend/src/components/layout/Header/__snapshots__/Header.test.tsx.snap index c8e836e0a6..2a1a5855eb 100644 --- a/source/frontend/src/components/layout/Header/__snapshots__/Header.test.tsx.snap +++ b/source/frontend/src/components/layout/Header/__snapshots__/Header.test.tsx.snap @@ -181,6 +181,7 @@ exports[`App Header renders correctly 1`] = `