From aac90d6145e2e277869f9ec914844a0e7dfbd8e7 Mon Sep 17 00:00:00 2001 From: Laurent Cazanove Date: Thu, 28 Nov 2024 15:25:48 +0800 Subject: [PATCH] Display Cloud banner on self-hosted instances (#564) * Display banner unless disabled * add tests * Update copy * Make Cloud banner sticky * Update Header top position when Cloud banner is closed * Adjust header height based on visible banners --- cypress/e2e/cloud-banner.cy.js | 23 ++++++++ src/App.js | 29 +++++++-- src/components/Body.js | 4 +- src/components/CloudBanner.js | 85 +++++++++------------------ src/components/Header/index.js | 9 ++- src/utils/isCloudBannerEnabled.js | 14 +++++ src/utils/shouldDisplayCloudBanner.js | 7 --- 7 files changed, 99 insertions(+), 72 deletions(-) create mode 100644 cypress/e2e/cloud-banner.cy.js create mode 100644 src/utils/isCloudBannerEnabled.js delete mode 100644 src/utils/shouldDisplayCloudBanner.js diff --git a/cypress/e2e/cloud-banner.cy.js b/cypress/e2e/cloud-banner.cy.js new file mode 100644 index 00000000..59af6917 --- /dev/null +++ b/cypress/e2e/cloud-banner.cy.js @@ -0,0 +1,23 @@ +const SELECTOR = '.cloud-banner' + +describe('Cloud Banner', () => { + it('should show cloud banner by default', () => { + cy.visit('/') + cy.get(SELECTOR).should('be.visible') + }) + + it('should hide cloud banner when cloud_banner=false query param is present', () => { + cy.visit('/?cloud_banner=false') + cy.get(SELECTOR).should('not.exist') + }) + + it('should show cloud banner with other query params when cloud_banner is not false', () => { + cy.visit('/?cloud_banner=true&other_param=123') + cy.get(SELECTOR).should('be.visible') + }) + + it('should show cloud banner with invalid cloud_banner values', () => { + cy.visit('/?cloud_banner=invalid') + cy.get(SELECTOR).should('be.visible') + }) +}) diff --git a/src/App.js b/src/App.js index f52eaf52..47d48699 100644 --- a/src/App.js +++ b/src/App.js @@ -16,7 +16,7 @@ import Modal from 'components/Modal' import NoMeilisearchRunning from 'components/NoMeilisearchRunning' import ApiKeyAwarenessBanner from 'components/ApiKeyAwarenessBanner' import getIndexesListWithStats from 'utils/getIndexesListWithStats' -import shouldDisplayCloudBanner from 'utils/shouldDisplayCloudBanner' +import isCloudBannerEnabled from 'utils/isCloudBannerEnabled' import shouldDisplayApiKeyModal from 'utils/shouldDisplayApiKeyModal' import hasAnApiKeySet from 'utils/hasAnApiKeySet' import clientAgents from './version/client-agents' @@ -78,9 +78,8 @@ const App = () => { }, []) useEffect(() => { - const shouldCloudBannerBeDisplayed = shouldDisplayCloudBanner() - if (shouldCloudBannerBeDisplayed) { - setShowCloudBanner(shouldCloudBannerBeDisplayed) + if (isCloudBannerEnabled()) { + setShowCloudBanner(true) } getApiKeyFromUrl() }, []) @@ -115,6 +114,20 @@ const App = () => { onClientUpdate() }, [meilisearchJsClient]) + const handleCloudBannerClose = () => { + setShowCloudBanner(false) + localStorage.setItem('bannerVisibility', JSON.stringify(false)) + } + + // Retrieve the banner visibility state from local storage on component mount + React.useEffect(() => { + const storedVisibility = localStorage.getItem('bannerVisibility') + if (storedVisibility) { + setShowCloudBanner(JSON.parse(storedVisibility)) + } + return () => {} + }, []) + return ( @@ -123,7 +136,12 @@ const App = () => { onClose={() => setIsApiKeyBannerVisible(false)} /> )} - {showCloudBanner && } + {showCloudBanner && ( + + )} {isMeilisearchRunning ? ( { requireApiKeyToWork={requireApiKeyToWork} getIndexesList={getIndexesList} isApiKeyBannerVisible={isApiKeyBannerVisible} + isCloudBannerVisible={showCloudBanner} /> ) : ( diff --git a/src/components/Body.js b/src/components/Body.js index 4bb7929b..f2b9a2b8 100644 --- a/src/components/Body.js +++ b/src/components/Body.js @@ -34,6 +34,7 @@ const Body = ({ setCurrentIndex, requireApiKeyToWork, isApiKeyBannerVisible, + isCloudBannerVisible, }) => { const { meilisearchJsClient, instantMeilisearchClient } = useMeilisearchClientContext() @@ -50,7 +51,8 @@ const Body = ({ requireApiKeyToWork={requireApiKeyToWork} client={meilisearchJsClient} refreshIndexes={getIndexesList} - isBannerVisible={isApiKeyBannerVisible} + isApiKeyBannerVisible={isApiKeyBannerVisible} + isCloudBannerVisible={isCloudBannerVisible} /> {/* */} diff --git a/src/components/CloudBanner.js b/src/components/CloudBanner.js index 10863262..2ad264c1 100644 --- a/src/components/CloudBanner.js +++ b/src/components/CloudBanner.js @@ -24,68 +24,41 @@ const CloudBannerWrapper = styled.div` top: 0; height: 74px; box-shadow: 0px 0px 30px ${(p) => Color(p.theme.colors.gray[0]).alpha(0.15)}; - z-index: 3; + z-index: 10; padding: 4px; ` -const CloudBanner = () => { - const [isBannerVisible, setIsBannerVisible] = React.useState(true) +const CloudBanner = ({ handleBannerClose, isBannerVisible }) => ( + <> + {isBannerVisible && ( + + + + Scale up with Meilisearch Cloud 🚀 + - const handleBannerClose = () => { - setIsBannerVisible(false) - localStorage.setItem('bannerVisibility', JSON.stringify(false)) - } - - // Retrieve the banner visibility state from local storage on component mount - React.useEffect(() => { - const storedVisibility = localStorage.getItem('bannerVisibility') - if (storedVisibility) { - setIsBannerVisible(JSON.parse(storedVisibility)) - } - - return () => {} - }, []) - - return ( - <> - {isBannerVisible && ( - - - - Supercharge your Meilisearch experience - - - - Say goodbye to server management, and manual updates with{' '} - - - Meilisearch Cloud - - - .  + + Faster, smarter search—no maintenance needed.{' '} + - Get started with a 14-day free trial! No credit card required. + Start free + + + {' '} + with no commitment. - - - - )} - - ) -} + + + + + )} + +) export default CloudBanner diff --git a/src/components/Header/index.js b/src/components/Header/index.js index 8916ccd6..f71107ae 100644 --- a/src/components/Header/index.js +++ b/src/components/Header/index.js @@ -24,7 +24,7 @@ const HeaderWrapper = styled('div')(compose(position), { position: 'sticky', height: '120px', boxShadow: `0px 0px 30px ${(p) => Color(p.theme.colors.gray[0]).alpha(0.15)}`, - zIndex: 3, + zIndex: 10, }) const ApiKey = ({ requireApiKeyToWork }) => { @@ -71,7 +71,8 @@ const Header = ({ setCurrentIndex, refreshIndexes, requireApiKeyToWork, - isBannerVisible, + isApiKeyBannerVisible, + isCloudBannerVisible, }) => { const { meilisearchJsClient } = useMeilisearchClientContext() const [version, setVersion] = React.useState() @@ -89,8 +90,10 @@ const Header = ({ getMeilisearchVersion() }, [meilisearchJsClient]) + const topPosition = + (isCloudBannerVisible ? 74 : 0) + (isApiKeyBannerVisible ? 55 : 0) return ( - + { + const urlParams = new URLSearchParams(window.location.search) + const cloudBannerQueryParam = urlParams.get(QUERY_PARAM_NAME) + return cloudBannerQueryParam !== 'false' +} + +export default isBannerEnabled diff --git a/src/utils/shouldDisplayCloudBanner.js b/src/utils/shouldDisplayCloudBanner.js deleted file mode 100644 index 949a4076..00000000 --- a/src/utils/shouldDisplayCloudBanner.js +++ /dev/null @@ -1,7 +0,0 @@ -const shouldDisplayCloudBanner = () => { - const urlParams = new URLSearchParams(window.location.search) - const cloudBannerQueryParam = urlParams.get('cloud_banner') - return cloudBannerQueryParam === 'true' -} - -export default shouldDisplayCloudBanner