diff --git a/cypress/e2e/Settings/settings.spec.js b/cypress/e2e/Settings/settings.spec.js index dbcca15f..cbd8cd96 100644 --- a/cypress/e2e/Settings/settings.spec.js +++ b/cypress/e2e/Settings/settings.spec.js @@ -14,6 +14,7 @@ import { updateMaxRateLimit, updateAutoAcceptIncidentQuery, updateAutoRefreshInterval, + updateDarkMode, manageIncidentTableColumns, manageCustomAlertColumnDefinitions, activateButton, @@ -193,4 +194,16 @@ describe('Manage Settings', { failFast: { enabled: false } }, () => { cy.get('.btn').contains('Clear Local Cache').click(); cy.get('.modal-title').contains('Disclaimer & License').should('be.visible'); }); + + it('Update dark mode', () => { + [true, false].forEach((darkMode) => { + updateDarkMode(darkMode); + cy.window() + .its('store') + .invoke('getState') + .then((state) => expect( + state.settings.darkMode, + ).to.equal(darkMode)); + }); + }); }); diff --git a/cypress/support/util/common.js b/cypress/support/util/common.js index b683b5b7..c8bff941 100644 --- a/cypress/support/util/common.js +++ b/cypress/support/util/common.js @@ -305,6 +305,22 @@ export const updateAutoAcceptIncidentQuery = (autoAcceptIncidentsQuery = false) cy.get('.close').click(); }; +export const updateDarkMode = (darkMode = false) => { + cy.get('.settings-panel-dropdown').click(); + cy.get('.dropdown-item').contains('Settings').click(); + cy.get('.nav-item').contains('User Profile').click(); + + if (darkMode) { + cy.get('#user-profile-dark-mode-checkbox').check({ force: true }); + } else { + cy.get('#user-profile-dark-mode-checkbox').uncheck({ force: true }); + } + + cy.get('.btn').contains('Update User Profile').click(); + checkActionAlertsModalContent('Updated user profile settings'); + cy.get('.close').click(); +}; + export const priorityNames = ['--', 'P5', 'P4', 'P3', 'P2', 'P1']; /* diff --git a/src/App.js b/src/App.js index 9cbbee7c..4bafb02e 100644 --- a/src/App.js +++ b/src/App.js @@ -97,6 +97,14 @@ const App = ({ }) => { // Verify if session token is present const token = sessionStorage.getItem('pd_access_token'); + const { + autoRefreshInterval, darkMode, + } = state.settings; + + if (darkMode) { + document.body.classList.add('dark-mode'); + } + if (!token) { return (
@@ -109,9 +117,6 @@ const App = ({ const { userAuthorized, userAcceptedDisclaimer, currentUserLocale, } = state.users; - const { - autoRefreshInterval, - } = state.settings; const { fetchingIncidents, fetchingIncidentNotes, diff --git a/src/App.scss b/src/App.scss index a5c82ee1..87d4429d 100644 --- a/src/App.scss +++ b/src/App.scss @@ -5,12 +5,22 @@ body { background-color: $pd-brand-background !important; } +body.dark-mode { + background-color: $pd-brand-gray-dark !important; +} + .btn-outline-secondary { color: $pd-brand-black; background-color: $pd-brand-white; border-color: $pd-brand-secondary-light; } +body.dark-mode .btn-outline-secondary { + color: $pd-white; + background-color: $pd-brand-gray-dark; + border-color: $pd-brand-secondary-light; +} + .btn-outline-secondary:disabled { color: $pd-brand-secondary-light !important; } @@ -26,6 +36,10 @@ body { background-color: unset !important; } +body.dark-mode .btn-outline-secondary:not(:disabled):not(.disabled).active { + background-color: $pd-brand-gray-light !important; +} + .btn-outline-dark { color: $pd-brand-gray-dark; background-color: $pd-white; @@ -59,3 +73,54 @@ body { .modal-footer { display: unset !important; } + +body.dark-mode a { + color: $pd-brand-white; +} + +body.dark-mode .modal-content { + background-color: $pd-brand-black !important; + color: $pd-white !important; +} + +body.dark-mode .modal-title { + color: $pd-brand-white !important; +} + +body.dark-mode .modal-header .close { + color: $pd-brand-white !important; +} + +body.dark-mode .form-label, body.dark-mode .form-text { + color: $pd-brand-white !important; +} + +body.dark-mode .form-control { + background-color: $pd-brand-gray-dark !important; + color: $pd-white; +} + +body.dark-mode div.react-select__control, body.dark-mode div.react-select__menu { + background-color: $pd-brand-gray-dark !important; + color: $pd-brand-white !important; +} + +body.dark-mode div.react-select__multi-value { + background-color: $pd-brand-gray-light !important; + color: $pd-brand-white !important; +} + +body.dark-mode div.react-select__single-value { + color: $pd-brand-white !important; +} + +body.dark-mode div.react-select__option:hover { + background-color: $pd-brand-secondary-light !important; + color: $pd-brand-white !important; +} + +body.dark-mode div.react-select__option--is-focused { + background-color: $pd-brand-secondary-light !important; + color: $pd-brand-white !important; +} + diff --git a/src/assets/images/pd_logo_white.svg b/src/assets/images/pd_logo_white.svg new file mode 100644 index 00000000..1394a06d --- /dev/null +++ b/src/assets/images/pd_logo_white.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/components/AddResponderModal/AddResponderModalComponent.js b/src/components/AddResponderModal/AddResponderModalComponent.js index 21dc4fc1..9c571c76 100644 --- a/src/components/AddResponderModal/AddResponderModalComponent.js +++ b/src/components/AddResponderModal/AddResponderModalComponent.js @@ -86,6 +86,7 @@ const AddResponderModalComponent = ({ {t('Responders')} ({ escalationPolicies: state.escalationPolicies.escalationPolicies, extensions: state.extensions, responsePlays: state.responsePlays.responsePlays, + settings: state.settings, }); const mapDispatchToProps = (dispatch) => ({ diff --git a/src/components/IncidentActions/IncidentActionsComponent.scss b/src/components/IncidentActions/IncidentActionsComponent.scss index 521dcc3f..2c2da7c9 100644 --- a/src/components/IncidentActions/IncidentActionsComponent.scss +++ b/src/components/IncidentActions/IncidentActionsComponent.scss @@ -11,6 +11,11 @@ background-color: $pd-white; } +body.dark-mode .incident-actions-ctr { + background-color: $pd-brand-gray-dark; + color: $pd-brand-white; +} + .action-button { margin-right: 10px; } diff --git a/src/components/IncidentActions/IncidentActionsComponent.test.js b/src/components/IncidentActions/IncidentActionsComponent.test.js index 29bc14e0..42508ef9 100644 --- a/src/components/IncidentActions/IncidentActionsComponent.test.js +++ b/src/components/IncidentActions/IncidentActionsComponent.test.js @@ -41,6 +41,9 @@ describe('IncidentActionsComponent', () => { serviceExtensionMap: {}, }, responsePlays: { responsePlays: [] }, + settings: { + darkMode: false, + }, }; const tempStoreMap = { ...baseStoreMap }; diff --git a/src/components/IncidentTable/IncidentTableComponent.scss b/src/components/IncidentTable/IncidentTableComponent.scss index 04957596..c997ce5b 100644 --- a/src/components/IncidentTable/IncidentTableComponent.scss +++ b/src/components/IncidentTable/IncidentTableComponent.scss @@ -7,6 +7,10 @@ top: 5px; } +body.dark-mode .querying-incidents { + color: $pd-brand-gray-light; +} + /* Empty Incidents */ .empty-incidents { height: 500px; @@ -17,6 +21,10 @@ padding-top: 20px; } +body.dark-mode .empty-incidents-badge { + color: $pd-brand-gray-light; +} + /* Table Positioning */ .incident-table { margin-top: 10px; @@ -142,6 +150,11 @@ border-right: 1px $pd-brand-background-3 solid; } +body.dark-mode .th { + background-color: $pd-gray; + color: $pd-white; +} + .th-sorted { padding: 10px !important; background-color: white; @@ -150,6 +163,11 @@ border-right: 1px $pd-brand-background-3 solid; } +body.dark-mode .th-sorted { + background-color: $pd-brand-secondary-dark; + color: $pd-white; +} + .resizer { display: inline-block; width: 2px; @@ -175,16 +193,32 @@ background-color: $pd-brand-background-2 !important; } +body.dark-mode .tr { + background-color: $pd-gray !important; + color: $pd-white; +} + .tr-odd { font-size: 14px; background-color: $pd-brand-background-3 !important; } +body.dark-mode .tr-odd { + background-color: $pd-gray-dark !important; + color: $pd-white; +} + .tr:hover, .tr-odd:hover { background-color: $pd-gray-medium !important; } +body.dark-mode .tr:hover, +body.dark-mode .tr-odd:hover { + background-color: $pd-brand-secondary-light !important; + color: $pd-white; +} + .td { padding: 10px !important; padding-left: 10px !important; diff --git a/src/components/MergeModal/MergeModalComponent.js b/src/components/MergeModal/MergeModalComponent.js index 2652053d..ad0dcbe4 100644 --- a/src/components/MergeModal/MergeModalComponent.js +++ b/src/components/MergeModal/MergeModalComponent.js @@ -93,6 +93,7 @@ const MergeModalComponent = ({ { const teamIdsInt = selectedTeams.map((team) => team.value); @@ -333,6 +341,7 @@ const QuerySettingsComponent = ({ { const escalationPolicyIdsInt = selectedEscalationPolicies.map( @@ -375,6 +385,7 @@ const QuerySettingsComponent = ({ { setAssignment(selectedAssignment); }} diff --git a/src/components/SettingsModal/SettingsModalComponent.js b/src/components/SettingsModal/SettingsModalComponent.js index 73ce86e2..5884c52b 100644 --- a/src/components/SettingsModal/SettingsModalComponent.js +++ b/src/components/SettingsModal/SettingsModalComponent.js @@ -39,6 +39,7 @@ import { setAutoAcceptIncidentsQuery as setAutoAcceptIncidentsQueryConnected, setAutoRefreshInterval as setAutoRefreshIntervalConnected, clearLocalCache as clearLocalCacheConnected, + setDarkMode as setDarkModeConnected, } from 'redux/settings/actions'; import { @@ -100,6 +101,7 @@ const SettingsModalComponent = ({ setMaxRateLimit, setAutoAcceptIncidentsQuery, setAutoRefreshInterval, + setDarkMode, clearLocalCache, updateActionAlertsModal, toggleDisplayActionAlertsModal, @@ -115,6 +117,7 @@ const SettingsModalComponent = ({ autoAcceptIncidentsQuery, autoRefreshInterval, alertCustomDetailFields, + darkMode, } = settings; const { incidentTableColumns, @@ -173,6 +176,8 @@ const SettingsModalComponent = ({ const [tempAutoAcceptQuery, setTempAutoAcceptQuery] = useState(autoAcceptIncidentsQuery); + const [tempDarkMode, setTempDarkMode] = useState(darkMode); + const [selectedColumns, setSelectedColumns] = useState( incidentTableColumns.map((column) => { // Recreate original value used from react-select in order to populate dual list @@ -253,6 +258,7 @@ const SettingsModalComponent = ({