Skip to content

Commit aac90d6

Browse files
authored
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
1 parent 412cba0 commit aac90d6

File tree

7 files changed

+99
-72
lines changed

7 files changed

+99
-72
lines changed

cypress/e2e/cloud-banner.cy.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const SELECTOR = '.cloud-banner'
2+
3+
describe('Cloud Banner', () => {
4+
it('should show cloud banner by default', () => {
5+
cy.visit('/')
6+
cy.get(SELECTOR).should('be.visible')
7+
})
8+
9+
it('should hide cloud banner when cloud_banner=false query param is present', () => {
10+
cy.visit('/?cloud_banner=false')
11+
cy.get(SELECTOR).should('not.exist')
12+
})
13+
14+
it('should show cloud banner with other query params when cloud_banner is not false', () => {
15+
cy.visit('/?cloud_banner=true&other_param=123')
16+
cy.get(SELECTOR).should('be.visible')
17+
})
18+
19+
it('should show cloud banner with invalid cloud_banner values', () => {
20+
cy.visit('/?cloud_banner=invalid')
21+
cy.get(SELECTOR).should('be.visible')
22+
})
23+
})

src/App.js

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import Modal from 'components/Modal'
1616
import NoMeilisearchRunning from 'components/NoMeilisearchRunning'
1717
import ApiKeyAwarenessBanner from 'components/ApiKeyAwarenessBanner'
1818
import getIndexesListWithStats from 'utils/getIndexesListWithStats'
19-
import shouldDisplayCloudBanner from 'utils/shouldDisplayCloudBanner'
19+
import isCloudBannerEnabled from 'utils/isCloudBannerEnabled'
2020
import shouldDisplayApiKeyModal from 'utils/shouldDisplayApiKeyModal'
2121
import hasAnApiKeySet from 'utils/hasAnApiKeySet'
2222
import clientAgents from './version/client-agents'
@@ -78,9 +78,8 @@ const App = () => {
7878
}, [])
7979

8080
useEffect(() => {
81-
const shouldCloudBannerBeDisplayed = shouldDisplayCloudBanner()
82-
if (shouldCloudBannerBeDisplayed) {
83-
setShowCloudBanner(shouldCloudBannerBeDisplayed)
81+
if (isCloudBannerEnabled()) {
82+
setShowCloudBanner(true)
8483
}
8584
getApiKeyFromUrl()
8685
}, [])
@@ -115,6 +114,20 @@ const App = () => {
115114
onClientUpdate()
116115
}, [meilisearchJsClient])
117116

117+
const handleCloudBannerClose = () => {
118+
setShowCloudBanner(false)
119+
localStorage.setItem('bannerVisibility', JSON.stringify(false))
120+
}
121+
122+
// Retrieve the banner visibility state from local storage on component mount
123+
React.useEffect(() => {
124+
const storedVisibility = localStorage.getItem('bannerVisibility')
125+
if (storedVisibility) {
126+
setShowCloudBanner(JSON.parse(storedVisibility))
127+
}
128+
return () => {}
129+
}, [])
130+
118131
return (
119132
<ApiKeyContext.Provider value={{ apiKey, setApiKey }}>
120133
<Wrapper>
@@ -123,7 +136,12 @@ const App = () => {
123136
onClose={() => setIsApiKeyBannerVisible(false)}
124137
/>
125138
)}
126-
{showCloudBanner && <CloudBanner />}
139+
{showCloudBanner && (
140+
<CloudBanner
141+
handleBannerClose={handleCloudBannerClose}
142+
isBannerVisible={showCloudBanner}
143+
/>
144+
)}
127145
{isMeilisearchRunning ? (
128146
<Body
129147
currentIndex={currentIndex}
@@ -132,6 +150,7 @@ const App = () => {
132150
requireApiKeyToWork={requireApiKeyToWork}
133151
getIndexesList={getIndexesList}
134152
isApiKeyBannerVisible={isApiKeyBannerVisible}
153+
isCloudBannerVisible={showCloudBanner}
135154
/>
136155
) : (
137156
<NoMeilisearchRunning />

src/components/Body.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const Body = ({
3434
setCurrentIndex,
3535
requireApiKeyToWork,
3636
isApiKeyBannerVisible,
37+
isCloudBannerVisible,
3738
}) => {
3839
const { meilisearchJsClient, instantMeilisearchClient } =
3940
useMeilisearchClientContext()
@@ -50,7 +51,8 @@ const Body = ({
5051
requireApiKeyToWork={requireApiKeyToWork}
5152
client={meilisearchJsClient}
5253
refreshIndexes={getIndexesList}
53-
isBannerVisible={isApiKeyBannerVisible}
54+
isApiKeyBannerVisible={isApiKeyBannerVisible}
55+
isCloudBannerVisible={isCloudBannerVisible}
5456
/>
5557
<BodyWrapper>
5658
{/* <Sidebar /> */}

src/components/CloudBanner.js

Lines changed: 29 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -24,68 +24,41 @@ const CloudBannerWrapper = styled.div`
2424
top: 0;
2525
height: 74px;
2626
box-shadow: 0px 0px 30px ${(p) => Color(p.theme.colors.gray[0]).alpha(0.15)};
27-
z-index: 3;
27+
z-index: 10;
2828
padding: 4px;
2929
`
3030

31-
const CloudBanner = () => {
32-
const [isBannerVisible, setIsBannerVisible] = React.useState(true)
31+
const CloudBanner = ({ handleBannerClose, isBannerVisible }) => (
32+
<>
33+
{isBannerVisible && (
34+
<CloudBannerWrapper className="cloud-banner">
35+
<Container display="flex" flexDirection="column" alignContent="center">
36+
<Typography variant="typo14" color="white">
37+
Scale up with Meilisearch Cloud 🚀
38+
</Typography>
3339

34-
const handleBannerClose = () => {
35-
setIsBannerVisible(false)
36-
localStorage.setItem('bannerVisibility', JSON.stringify(false))
37-
}
38-
39-
// Retrieve the banner visibility state from local storage on component mount
40-
React.useEffect(() => {
41-
const storedVisibility = localStorage.getItem('bannerVisibility')
42-
if (storedVisibility) {
43-
setIsBannerVisible(JSON.parse(storedVisibility))
44-
}
45-
46-
return () => {}
47-
}, [])
48-
49-
return (
50-
<>
51-
{isBannerVisible && (
52-
<CloudBannerWrapper>
53-
<Container
54-
display="flex"
55-
flexDirection="column"
56-
alignContent="center"
57-
>
58-
<Typography variant="typo14" color="white">
59-
Supercharge your Meilisearch experience
60-
</Typography>
61-
62-
<Typography variant="typo15" color="white">
63-
Say goodbye to server management, and manual updates with{' '}
64-
<Link
65-
href="https://www.meilisearch.com/cloud?utm_campaign=oss&utm_source=integration&utm_medium=minidashboard"
66-
color="white"
67-
>
68-
<Typography variant="typo14" color="white">
69-
Meilisearch Cloud
70-
</Typography>
71-
</Link>
72-
.&nbsp;
40+
<Typography variant="typo15" color="white">
41+
Faster, smarter search—no maintenance needed.{' '}
42+
<Link
43+
href="https://www.meilisearch.com/cloud?utm_campaign=oss&utm_source=integration&utm_medium=minidashboard"
44+
color="white"
45+
>
7346
<Typography variant="typo14" color="white">
74-
Get started with a 14-day free trial! No credit card required.
47+
Start free
7548
</Typography>
49+
</Link>
50+
<Typography variant="typo14" color="white">
51+
{' '}
52+
with no commitment.
7653
</Typography>
77-
<Button
78-
color="gray.9"
79-
aria-label="close"
80-
onClick={handleBannerClose}
81-
>
82-
<Cross style={{ width: 10 }} />
83-
</Button>
84-
</Container>
85-
</CloudBannerWrapper>
86-
)}
87-
</>
88-
)
89-
}
54+
</Typography>
55+
<Button color="gray.9" aria-label="close" onClick={handleBannerClose}>
56+
<Cross style={{ width: 10 }} />
57+
</Button>
58+
</Container>
59+
</CloudBannerWrapper>
60+
)}
61+
</>
62+
)
9063

9164
export default CloudBanner

src/components/Header/index.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const HeaderWrapper = styled('div')(compose(position), {
2424
position: 'sticky',
2525
height: '120px',
2626
boxShadow: `0px 0px 30px ${(p) => Color(p.theme.colors.gray[0]).alpha(0.15)}`,
27-
zIndex: 3,
27+
zIndex: 10,
2828
})
2929

3030
const ApiKey = ({ requireApiKeyToWork }) => {
@@ -71,7 +71,8 @@ const Header = ({
7171
setCurrentIndex,
7272
refreshIndexes,
7373
requireApiKeyToWork,
74-
isBannerVisible,
74+
isApiKeyBannerVisible,
75+
isCloudBannerVisible,
7576
}) => {
7677
const { meilisearchJsClient } = useMeilisearchClientContext()
7778
const [version, setVersion] = React.useState()
@@ -89,8 +90,10 @@ const Header = ({
8990
getMeilisearchVersion()
9091
}, [meilisearchJsClient])
9192

93+
const topPosition =
94+
(isCloudBannerVisible ? 74 : 0) + (isApiKeyBannerVisible ? 55 : 0)
9295
return (
93-
<HeaderWrapper top={isBannerVisible ? 55 : 0}>
96+
<HeaderWrapper top={topPosition}>
9497
<Container
9598
p={4}
9699
display="flex"

src/utils/isCloudBannerEnabled.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/** @const {string} Name of the query parameter that controls banner visibility */
2+
const QUERY_PARAM_NAME = 'cloud_banner'
3+
4+
/**
5+
* Checks if the cloud banner should be enabled based on URL query parameters
6+
* @returns {boolean} True if banner should be shown, false if explicitly disabled via query param
7+
*/
8+
const isBannerEnabled = () => {
9+
const urlParams = new URLSearchParams(window.location.search)
10+
const cloudBannerQueryParam = urlParams.get(QUERY_PARAM_NAME)
11+
return cloudBannerQueryParam !== 'false'
12+
}
13+
14+
export default isBannerEnabled

src/utils/shouldDisplayCloudBanner.js

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)