diff --git a/.env b/.env
index 2b6ea295c..8407c826d 100644
--- a/.env
+++ b/.env
@@ -16,3 +16,4 @@ NEW_RELIC_APP_ID=null
NEW_RELIC_LICENSE_KEY=null
APP_ID=''
MFE_CONFIG_API_URL=''
+SITEWIDE_BANNER_CONTENT = ""
diff --git a/.env.development b/.env.development
index 1dd00d5c3..334b6ab5b 100644
--- a/.env.development
+++ b/.env.development
@@ -21,3 +21,4 @@ ADDITIONAL_METADATA_REQUIRED_FIELDS='{}'
IS_NEW_SLUG_FORMAT_ENABLED='false'
MARKETING_SITE_PREVIEW_URL_ROOT=''
COURSE_URL_SLUGS_PATTERN = '{}'
+SITEWIDE_BANNER_CONTENT = ""
diff --git a/src/components/SitewideBanner/SitewideBanner.test.jsx b/src/components/SitewideBanner/SitewideBanner.test.jsx
new file mode 100644
index 000000000..fdd5aae6d
--- /dev/null
+++ b/src/components/SitewideBanner/SitewideBanner.test.jsx
@@ -0,0 +1,50 @@
+import React from "react";
+import { shallow } from "enzyme";
+import Cookies from "js-cookie";
+import { Alert } from "react-bootstrap";
+import SitewideBanner from "./index";
+
+describe("SitewideBanner", () => {
+ it("renders correctly when visible", () => {
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper.find(Alert).props().variant).toBe("success");
+ expect(wrapper.find(Alert).props().dismissible).toBe(true);
+ const alertContent = wrapper.find(Alert).dive().find("div").first().html();
+ expect(alertContent).toContain("Dummy Message");
+ });
+
+ it("calls handleDismiss and sets cookie when dismissed", () => {
+ const setCookieMock = jest.spyOn(Cookies, "set");
+ const wrapper = shallow(
+
+ );
+ wrapper.find(Alert).simulate("close");
+ expect(wrapper.isEmptyRender()).toBe(true);
+ expect(setCookieMock).toHaveBeenCalledWith("bannerCookie", "true", {
+ expires: 7,
+ });
+ setCookieMock.mockRestore();
+ });
+
+ it("handles non-dismissible banner correctly", () => {
+ const wrapper = shallow(
+
+ );
+ expect(wrapper.find(Alert).props().dismissible).toBe(false);
+ });
+});
diff --git a/src/components/SitewideBanner/index.jsx b/src/components/SitewideBanner/index.jsx
new file mode 100644
index 000000000..2bdcb5bc3
--- /dev/null
+++ b/src/components/SitewideBanner/index.jsx
@@ -0,0 +1,55 @@
+import React, { useState, useEffect } from "react";
+import PropTypes from "prop-types";
+import Cookies from "js-cookie";
+import { Alert, Container } from "react-bootstrap";
+
+const SitewideBanner = ({ message, type, dismissible, cookieName, cookieExpiryDays }) => {
+ const [isVisible, setIsVisible] = useState(true);
+
+ useEffect(() => {
+ if (cookieName && Cookies.get(cookieName)) {
+ setIsVisible(false);
+ }
+ }, [cookieName]);
+
+ const handleDismiss = () => {
+ setIsVisible(false);
+ if (cookieName) {
+ Cookies.set(cookieName, "true", { expires: cookieExpiryDays });
+ }
+ };
+
+ if (!isVisible) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+ );
+};
+
+SitewideBanner.propTypes = {
+ message: PropTypes.string.isRequired,
+ type: PropTypes.oneOf(["primary", "success", "warning", "danger", "info", "secondary", "light", "dark"]),
+ dismissible: PropTypes.bool,
+ cookieName: PropTypes.string,
+ cookieExpiryDays: PropTypes.number,
+};
+
+SitewideBanner.defaultProps = {
+ type: "info",
+ dismissible: false,
+ cookieName: null,
+ cookieExpiryDays: 7,
+};
+
+export default SitewideBanner;
diff --git a/src/components/SitewideBanner/styles.scss b/src/components/SitewideBanner/styles.scss
new file mode 100644
index 000000000..cb13ca933
--- /dev/null
+++ b/src/components/SitewideBanner/styles.scss
@@ -0,0 +1,14 @@
+
+ .banner__dismiss {
+ background: none;
+ border: none;
+ font-size: 1.5rem;
+ line-height: 1;
+ color: inherit;
+ position: absolute;
+ right: 1rem;
+ top: 50%;
+ transform: translateY(-50%);
+ cursor: pointer;
+ }
+
\ No newline at end of file