From 4542069ec2abb943bbf9bb10699f1eda9a7b7625 Mon Sep 17 00:00:00 2001 From: Viktor Rusakov Date: Thu, 2 Feb 2023 09:53:44 +0200 Subject: [PATCH] feat: add ability to dynamically load theme overrides --- .env.development | 3 ++- .env.test | 1 + src/config.js | 2 ++ src/react/AppProvider.jsx | 26 +++++++++++++++++++++++--- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/.env.development b/.env.development index 21974f265..9b8471f9d 100644 --- a/.env.development +++ b/.env.development @@ -27,4 +27,5 @@ FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico IGNORED_ERROR_REGEX= MFE_CONFIG_API_URL= APP_ID= -SUPPORT_URL=https://support.edx.org \ No newline at end of file +SUPPORT_URL=https://support.edx.org +THEME_OVERRIDE_URL= diff --git a/.env.test b/.env.test index f25231a3b..9b8471f9d 100644 --- a/.env.test +++ b/.env.test @@ -28,3 +28,4 @@ IGNORED_ERROR_REGEX= MFE_CONFIG_API_URL= APP_ID= SUPPORT_URL=https://support.edx.org +THEME_OVERRIDE_URL= diff --git a/src/config.js b/src/config.js index 139ac5415..b316e19d6 100644 --- a/src/config.js +++ b/src/config.js @@ -72,6 +72,7 @@ let config = { MFE_CONFIG_API_URL: process.env.MFE_CONFIG_API_URL, APP_ID: process.env.APP_ID, SUPPORT_URL: process.env.SUPPORT_URL, + THEME_OVERRIDE_URL: process.env.THEME_OVERRIDE_URL, }; /** @@ -203,4 +204,5 @@ export function ensureConfig(keys, requester = 'unspecified application code') { * @property {string} MFE_CONFIG_API_URL * @property {string} APP_ID * @property {string} SUPPORT_URL + * @property {string} THEME_OVERRIDE_URL */ diff --git a/src/react/AppProvider.jsx b/src/react/AppProvider.jsx index 36bc12037..823d96680 100644 --- a/src/react/AppProvider.jsx +++ b/src/react/AppProvider.jsx @@ -1,4 +1,4 @@ -import React, { useState, useMemo } from 'react'; +import React, { useState, useMemo, useEffect } from 'react'; import PropTypes from 'prop-types'; import { Router } from 'react-router-dom'; @@ -48,6 +48,7 @@ export default function AppProvider({ store, children }) { const [config, setConfig] = useState(getConfig()); const [authenticatedUser, setAuthenticatedUser] = useState(getAuthenticatedUser()); const [locale, setLocale] = useState(getLocale()); + const [themeLoaded, setThemeLoaded] = useState(false); useAppEvent(AUTHENTICATED_USER_CHANGED, () => { setAuthenticatedUser(getAuthenticatedUser()); @@ -61,8 +62,28 @@ export default function AppProvider({ store, children }) { setLocale(getLocale()); }); + useEffect(() => { + if (config.THEME_OVERRIDE_URL) { + const themeLink = document.createElement('link'); + themeLink.href = config.THEME_OVERRIDE_URL; + themeLink.rel = 'stylesheet'; + themeLink.type = 'text/css'; + themeLink.onload = () => setThemeLoaded(true); + themeLink.onerror = () => setThemeLoaded(true); + + document.head.appendChild(themeLink); + + return () => document.head.removeChild(themeLink); + } + setThemeLoaded(true); + }, [config.THEME_OVERRIDE_URL]); + const appContextValue = useMemo(() => ({ authenticatedUser, config, locale }), [authenticatedUser, config, locale]); + if (!themeLoaded) { + return null; + } + return ( @@ -81,8 +102,7 @@ export default function AppProvider({ store, children }) { } AppProvider.propTypes = { - // eslint-disable-next-line react/forbid-prop-types - store: PropTypes.object, + store: PropTypes.shape({}), children: PropTypes.node.isRequired, };