diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 59d6eb294b3c..aa6b51940694 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -155,6 +155,7 @@ src/applications/static-pages/health-care-manage-benefits/view-test-and-lab-resu src/applications/facility-locator @department-of-veterans-affairs/vfs-facilities-frontend @department-of-veterans-affairs/va-platform-cop-frontend src/applications/static-pages/facilities @department-of-veterans-affairs/vfs-facilities-frontend @department-of-veterans-affairs/va-platform-cop-frontend src/applications/static-pages/tests/facilities @department-of-veterans-affairs/vfs-facilities-frontend @department-of-veterans-affairs/va-platform-cop-frontend +src/applications/static-pages/situation-updates-banner @department-of-veterans-affairs/vfs-facilities-frontend @department-of-veterans-affairs/va-platform-cop-frontend # Caregiver diff --git a/script/watch.js b/script/watch.js index aee70cf27a21..d2a854d53787 100644 --- a/script/watch.js +++ b/script/watch.js @@ -2,7 +2,7 @@ const argv = require('minimist')(process.argv.slice(2)); const printBuildHelp = require('./build-help'); const { runCommand } = require('./utils'); -// Preset memory options 1gb -> 8gb +// Preset memory options 1gb -> 12gb const memoryOptions = [1024, 2048, 3072, 4096, 5120, 6144, 7168, 8192, 12288]; // Caching the input memory arg diff --git a/src/applications/static-pages/situation-updates-banner/createSituationUpdatesBanner.jsx b/src/applications/static-pages/situation-updates-banner/createSituationUpdatesBanner.jsx new file mode 100644 index 000000000000..326c302b9e3c --- /dev/null +++ b/src/applications/static-pages/situation-updates-banner/createSituationUpdatesBanner.jsx @@ -0,0 +1,54 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Provider } from 'react-redux'; +import { useFeatureToggle } from '~/platform/utilities/feature-toggles'; +import SituationUpdateBanner from './situationUpdateBanner'; + +export const BannerContainer = () => { + const { + TOGGLE_NAMES, + useToggleValue, + useToggleLoadingValue, + } = useFeatureToggle(); + + const alternativeBannersEnabled = useToggleValue( + TOGGLE_NAMES.bannerUseAlternativeBanners, + ); + const isLoadingFeatureFlags = useToggleLoadingValue(); + + if (isLoadingFeatureFlags || !alternativeBannersEnabled) { + return null; + } + + const defaultProps = { + id: '1', + bundle: 'situation-updates', + headline: 'Situation update', + alertType: 'warning', + content: + "We're having issues at this location. Please avoid this facility until further notice.", + context: 'global', + showClose: true, + operatingStatusCTA: false, + emailUpdatesButton: false, + findFacilitiesCTA: false, + limitSubpageInheritance: false, + }; + + return ; +}; + +export default async function createSituationUpdatesBanner(store, widgetType) { + const bannerWidget = document.querySelector( + `[data-widget-type="${widgetType}"]`, + ); + + if (bannerWidget) { + ReactDOM.render( + + + , + bannerWidget, + ); + } +} diff --git a/src/applications/static-pages/situation-updates-banner/situationUpdateBanner.jsx b/src/applications/static-pages/situation-updates-banner/situationUpdateBanner.jsx new file mode 100644 index 000000000000..e1dd008d86d0 --- /dev/null +++ b/src/applications/static-pages/situation-updates-banner/situationUpdateBanner.jsx @@ -0,0 +1,29 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export default function SituationUpdateBanner({ + id, + alertType, + headline, + showClose, + content, +}) { + return ( + +

{content}

+
+ ); +} + +SituationUpdateBanner.propTypes = { + alertType: PropTypes.string.isRequired, + content: PropTypes.node.isRequired, + headline: PropTypes.string.isRequired, + id: PropTypes.string.isRequired, + showClose: PropTypes.bool, +}; diff --git a/src/applications/static-pages/situation-updates-banner/tests/createSituationUpdatesBanner.unit.spec.js b/src/applications/static-pages/situation-updates-banner/tests/createSituationUpdatesBanner.unit.spec.js new file mode 100644 index 000000000000..7723cb5a2548 --- /dev/null +++ b/src/applications/static-pages/situation-updates-banner/tests/createSituationUpdatesBanner.unit.spec.js @@ -0,0 +1,75 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import ReactDOM from 'react-dom'; +import { Provider } from 'react-redux'; + +import createSituationUpdatesBanner, { + BannerContainer, +} from '../createSituationUpdatesBanner'; +import widgetTypes from '../../widgetTypes'; + +describe('createSituationUpdatesBanner', () => { + let sandbox; + let store; + let widgetContainer; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + store = { + getState: () => ({ + featureToggles: { + loading: false, + bannerUseAlternativeBanners: true, + }, + }), + subscribe: () => {}, + dispatch: sinon.spy(), + }; + + widgetContainer = document.createElement('div'); + widgetContainer.setAttribute( + 'data-widget-type', + widgetTypes.SITUATION_UPDATES_BANNER, + ); + document.body.appendChild(widgetContainer); + + sandbox.stub(ReactDOM, 'render'); + }); + + afterEach(() => { + sandbox.restore(); + if (widgetContainer && widgetContainer.parentNode) { + widgetContainer.parentNode.removeChild(widgetContainer); + } + }); + + it('should not render banner when widget container is not found', async () => { + document.body.removeChild(widgetContainer); + await createSituationUpdatesBanner( + store, + widgetTypes.SITUATION_UPDATES_BANNER, + ); + expect(ReactDOM.render.called).to.be.false; + }); + + it('should render banner with default props when widget container exists', async () => { + await createSituationUpdatesBanner( + store, + widgetTypes.SITUATION_UPDATES_BANNER, + ); + + expect(ReactDOM.render.calledOnce).to.be.true; + + const renderCall = ReactDOM.render.getCall(0); + const [element, container] = renderCall.args; + + expect(container).to.equal(widgetContainer); + + // Check if rendered element is wrapped in Provider + expect(element.type).to.equal(Provider); + + // Check if SituationUpdateBanner is rendered with correct props + const situationBanner = element.props.children; + expect(situationBanner.type).to.equal(BannerContainer); + }); +}); diff --git a/src/applications/static-pages/static-pages-entry.js b/src/applications/static-pages/static-pages-entry.js index 4b1c2ff24c75..244d4cd1c494 100644 --- a/src/applications/static-pages/static-pages-entry.js +++ b/src/applications/static-pages/static-pages-entry.js @@ -64,6 +64,7 @@ import createPost911GiBillStatusWidget, { post911GIBillStatusReducer, } from '../post-911-gib-status/createPost911GiBillStatusWidget'; import createResourcesAndSupportSearchWidget from './widget-creators/resources-and-support-search'; +import createSituationUpdatesBanner from './situation-updates-banner/createSituationUpdatesBanner'; import createThirdPartyApps, { thirdPartyAppsReducer, } from '../third-party-app-directory/createThirdPartyApps'; @@ -200,6 +201,7 @@ createScheduleViewVAAppointmentsPage( widgetTypes.SCHEDULE_VIEW_VA_APPOINTMENTS_PAGE, ); createSecureMessagingPage(store, widgetTypes.SECURE_MESSAGING_PAGE); +createSituationUpdatesBanner(store, widgetTypes.SITUATION_UPDATES_BANNER); createViewTestAndLabResultsPage( store, widgetTypes.VIEW_TEST_AND_LAB_RESULTS_PAGE, diff --git a/src/applications/static-pages/widgetTypes.js b/src/applications/static-pages/widgetTypes.js index 287c49be0c58..c128249c0661 100644 --- a/src/applications/static-pages/widgetTypes.js +++ b/src/applications/static-pages/widgetTypes.js @@ -74,6 +74,7 @@ export default { SCO_EVENTS: 'sco-events', SECURE_MESSAGING_PAGE: 'secure-messaging-page', SIDE_NAV: 'side-nav', + SITUATION_UPDATES_BANNER: 'situation-updates-banner', SUPPLEMENTAL_CLAIM: 'supplemental_claim', THIRD_PARTY_APP_DIRECTORY: 'third-party-app-directory', VET_CENTER_HOURS: 'vet-center-hours', diff --git a/src/platform/utilities/feature-toggles/featureFlagNames.json b/src/platform/utilities/feature-toggles/featureFlagNames.json index 1c039ec255b4..2599a906133c 100644 --- a/src/platform/utilities/feature-toggles/featureFlagNames.json +++ b/src/platform/utilities/feature-toggles/featureFlagNames.json @@ -7,6 +7,7 @@ "askVaIntroductionPageFeature": "ask_va_introduction_page_feature", "authExpVbaDowntimeMessage": "auth_exp_vba_downtime_message", "avsEnabled": "avs_enabled", + "bannerUseAlternativeBanners": "banner_use_alternative_banners", "bcasLettersUseLighthouse": "bcas_letters_use_lighthouse", "benefitsDocumentsUseLighthouse": "benefits_documents_use_lighthouse", "burialFormEnabled": "burial_form_enabled",