diff --git a/.env b/.env
new file mode 100644
index 00000000..55a88b4b
--- /dev/null
+++ b/.env
@@ -0,0 +1,2 @@
+REACT_APP_HUBSPOT_PORTAL_ID=25945010
+REACT_APP_HUBSPOT_FORM_GUID=991e2a09-77c2-4428-9242-ebf26bfc6c64
diff --git a/jsconfig.json b/jsconfig.json
index 5875dc5b..ef66e1dc 100644
--- a/jsconfig.json
+++ b/jsconfig.json
@@ -1,6 +1,7 @@
{
"compilerOptions": {
- "baseUrl": "src"
+ "baseUrl": "src",
+ "jsx": "react"
},
"include": ["src"]
}
diff --git a/src/components/NewsletterForm.jsx b/src/components/NewsletterForm.jsx
new file mode 100644
index 00000000..be78b344
--- /dev/null
+++ b/src/components/NewsletterForm.jsx
@@ -0,0 +1,74 @@
+import React from 'react'
+import styled from 'styled-components'
+
+import useNewsletter from 'hooks/useNewsletter'
+
+const Input = styled.input`
+ width: 100%;
+ padding: 0.75rem 1rem;
+ border: 1px solid ${(p) => p.theme.colors.gray[8]};
+ border-radius: 8px;
+ font-size: 0.875rem;
+ color: ${(p) => p.theme.colors.gray[0]};
+ background: ${(p) => p.theme.colors.white};
+ margin: 1rem 0;
+
+ &:focus {
+ outline: none;
+ border-color: ${(p) => p.theme.colors.main.default};
+ }
+
+ &::placeholder {
+ color: ${(p) => p.theme.colors.gray[6]};
+ }
+`
+
+const Button = styled.button`
+ width: 100%;
+ padding: 0.75rem 1rem;
+ background: ${(p) => p.theme.colors.main.default};
+ color: white;
+ border: none;
+ border-radius: 8px;
+ font-size: 0.875rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: background-color 0.2s;
+
+ &:hover {
+ background: ${(p) => p.theme.colors.main.dark};
+ }
+`
+
+const NewsletterForm = () => {
+ const [email, setEmail] = React.useState('')
+ const { subscribe } = useNewsletter()
+ const [status, setStatus] = React.useState('idle')
+
+ const handleSubmit = (e) => {
+ e.preventDefault()
+ setStatus('loading')
+ subscribe(email)
+ .then(() => setStatus('success'))
+ .catch(() => setStatus('error'))
+ }
+
+ return (
+
+ )
+}
+
+export default NewsletterForm
diff --git a/src/components/RightPanel.jsx b/src/components/RightPanel.jsx
new file mode 100644
index 00000000..a43c168a
--- /dev/null
+++ b/src/components/RightPanel.jsx
@@ -0,0 +1,193 @@
+import React from 'react'
+import styled from 'styled-components'
+import Link from 'components/Link'
+import Typography from 'components/Typography'
+import AcademicHatIcon from 'components/icons/heroicons/AcademicHatIcon'
+import LifebuoyIcon from 'components/icons/heroicons/LifebuoyIcon'
+import ChatBubbleIcon from 'components/icons/heroicons/ChatBubbleIcon'
+import MenuBarsIcon from 'components/icons/heroicons/MenuBarsIcon'
+import NewsletterForm from 'components/NewsletterForm'
+
+const PanelWrapper = styled.div`
+ position: fixed;
+ top: 0;
+ right: 0;
+ width: 430px;
+ height: 100vh;
+ background: ${(p) => p.theme.colors.white};
+ box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1);
+ transform: translateX(${({ isOpen }) => (isOpen ? '0' : '100%')});
+ transition: transform 0.3s ease-in-out;
+ padding: 2rem 2.5rem;
+ z-index: 10;
+`
+
+const Title = styled.h2`
+ color: ${(p) => p.theme.colors.main.default};
+ font-size: 1.5rem;
+ font-weight: 600;
+ margin: 0;
+`
+
+const SectionTitle = styled.h3`
+ color: ${(p) => p.theme.colors.gray[0]};
+ font-weight: 600;
+ margin-bottom: 2rem;
+`
+
+const Header = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 3rem;
+ margin-bottom: 2rem;
+`
+
+const CloseButton = styled.button`
+ background: none;
+ border: none;
+ cursor: pointer;
+ font-size: 2rem;
+ color: ${(p) => p.theme.colors.gray[0]};
+ padding: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ svg {
+ width: 24px;
+ height: 24px;
+ color: ${(p) => p.theme.colors.gray[0]};
+ }
+`
+
+const HelpLink = styled(Link)`
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ padding: 0.75rem 1rem;
+ margin-bottom: 0.75rem;
+ background-color: transparent;
+ border: 1px solid ${(p) => p.theme.colors.gray[10]};
+ border-radius: 8px;
+ box-shadow: 0px 4px 6px ${(p) => p.theme.colors.gray[10]}10;
+ text-decoration: none;
+ color: ${(p) => p.theme.colors.gray[0]};
+ transition: all 0.2s;
+
+ svg {
+ width: 20px;
+ height: 20px;
+ color: ${(p) => p.theme.colors.gray[0]};
+ transition: color 0.1s;
+ }
+
+ &:hover {
+ box-shadow: none;
+ border-color: ${(p) => p.theme.colors.main.default};
+ text-decoration: none;
+ color: ${(p) => p.theme.colors.gray[0]};
+
+ svg {
+ // color: ${(p) => p.theme.colors.main.default};
+ }
+ }
+`
+
+const Section = styled.div`
+ margin-bottom: 2.5rem;
+`
+const CloudButton = styled.button`
+ width: 100%;
+ padding: 0.75rem 1rem;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ gap: 0.75rem;
+ background: linear-gradient(269.85deg, #ff1786 0%, #8e33de 100%);
+ color: white;
+ border: none;
+ border-radius: 8px;
+ font-size: 0.875rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: transform 0.2s;
+
+ &:hover {
+ transform: translateY(-2px);
+ }
+`
+
+const CloudCardLink = styled(Link)`
+ text-decoration: none;
+ display: block;
+
+ &:hover {
+ text-decoration: none;
+ }
+`
+
+const RightPanel = ({ isOpen, onClose }) => (
+
+
+ Getting started
+
+
+
+
+
+
+ Try Meilisearch Cloud
+
+ Streamline your experience with search analytics, monitoring, and more.
+
+
+ Start free trial
+
+
+
+
+ Need help?
+
+ Check out our resources to get started.
+
+
+
+
+ Documentation
+
+
+
+ Help center
+
+
+
+ Community
+
+
+
+
+ Stay up to date
+
+ Get monthly updates about new features and tips to get the the most out
+ of Meilisearch.
+
+
+
+
+)
+
+export default RightPanel
diff --git a/src/components/RightPanel/index.js b/src/components/RightPanel/index.js
deleted file mode 100644
index b7444f71..00000000
--- a/src/components/RightPanel/index.js
+++ /dev/null
@@ -1,248 +0,0 @@
-import React from 'react'
-import styled from 'styled-components'
-import Link from 'components/Link'
-import Typography from 'components/Typography'
-import AcademicHatIcon from 'components/icons/heroicons/AcademicHatIcon'
-import LifebuoyIcon from 'components/icons/heroicons/LifebuoyIcon'
-import ChatBubbleIcon from 'components/icons/heroicons/ChatBubbleIcon'
-import MenuBarsIcon from 'components/icons/heroicons/MenuBarsIcon'
-
-const PanelWrapper = styled.div`
- position: fixed;
- top: 0;
- right: 0;
- width: 430px;
- height: 100vh;
- background: ${(p) => p.theme.colors.white};
- box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1);
- transform: translateX(${({ isOpen }) => (isOpen ? '0' : '100%')});
- transition: transform 0.3s ease-in-out;
- padding: 2rem 2.5rem;
- z-index: 10;
-`
-
-const Title = styled.h2`
- color: ${(p) => p.theme.colors.main.default};
- font-size: 1.5rem;
- font-weight: 600;
- margin: 0;
-`
-
-const SectionTitle = styled.h3`
- color: ${(p) => p.theme.colors.gray[0]};
- font-weight: 600;
- margin-bottom: 2rem;
-`
-
-const Header = styled.div`
- display: flex;
- align-items: center;
- justify-content: space-between;
- height: 3rem;
- margin-bottom: 2rem;
-`
-
-const CloseButton = styled.button`
- background: none;
- border: none;
- cursor: pointer;
- font-size: 2rem;
- color: ${(p) => p.theme.colors.gray[0]};
- padding: 0;
- display: flex;
- align-items: center;
- justify-content: center;
-
- svg {
- width: 24px;
- height: 24px;
- color: ${(p) => p.theme.colors.gray[0]};
- }
-`
-
-const HelpLink = styled(Link)`
- display: flex;
- align-items: center;
- gap: 0.75rem;
- padding: 0.75rem 1rem;
- margin-bottom: 0.75rem;
- background-color: transparent;
- border: 1px solid ${(p) => p.theme.colors.gray[10]};
- border-radius: 8px;
- box-shadow: 0px 4px 6px ${(p) => p.theme.colors.gray[10]}10;
- text-decoration: none;
- color: ${(p) => p.theme.colors.gray[0]};
- transition: all 0.2s;
-
- svg {
- width: 20px;
- height: 20px;
- color: ${(p) => p.theme.colors.gray[0]};
- transition: color 0.1s;
- }
-
- &:hover {
- box-shadow: none;
- border-color: ${(p) => p.theme.colors.main.default};
- text-decoration: none;
- color: ${(p) => p.theme.colors.gray[0]};
-
- svg {
- // color: ${(p) => p.theme.colors.main.default};
- }
- }
-`
-
-const Input = styled.input`
- width: 100%;
- padding: 0.75rem 1rem;
- border: 1px solid ${(p) => p.theme.colors.gray[8]};
- border-radius: 8px;
- font-size: 0.875rem;
- color: ${(p) => p.theme.colors.gray[0]};
- background: ${(p) => p.theme.colors.white};
- margin: 1rem 0;
-
- &:focus {
- outline: none;
- border-color: ${(p) => p.theme.colors.main.default};
- }
-
- &::placeholder {
- color: ${(p) => p.theme.colors.gray[6]};
- }
-`
-
-const Button = styled.button`
- width: 100%;
- padding: 0.75rem 1rem;
- background: ${(p) => p.theme.colors.main.default};
- color: white;
- border: none;
- border-radius: 8px;
- font-size: 0.875rem;
- font-weight: 600;
- cursor: pointer;
- transition: background-color 0.2s;
-
- &:hover {
- background: ${(p) => p.theme.colors.main.dark};
- }
-`
-
-const Section = styled.div`
- margin-bottom: 2.5rem;
-`
-const CloudButton = styled.button`
- width: 100%;
- padding: 0.75rem 1rem;
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- gap: 0.75rem;
- background: linear-gradient(269.85deg, #ff1786 0%, #8e33de 100%);
- color: white;
- border: none;
- border-radius: 8px;
- font-size: 0.875rem;
- font-weight: 600;
- cursor: pointer;
- transition: transform 0.2s;
-
- &:hover {
- transform: translateY(-2px);
- }
-`
-
-const CloudCardLink = styled(Link)`
- text-decoration: none;
- display: block;
-
- &:hover {
- text-decoration: none;
- }
-`
-
-const RightPanel = ({ isOpen, onClose }) => {
- const [email, setEmail] = React.useState('')
-
- const handleSubscribe = (e) => {
- e.preventDefault()
- // TODO: Implement newsletter subscription
- console.log('Subscribe with email:', email)
- }
-
- return (
-
-
- Getting started
-
-
-
-
-
-
- Try Meilisearch Cloud
-
- Streamline your experience with search analytics, monitoring, and
- more.
-
-
- Start free trial
-
-
-
-
- Need help?
-
- Check out our resources to get started.
-
-
-
-
- Documentation
-
-
-
- Help center
-
-
-
- Community
-
-
-
-
- Stay up to date
-
- Get monthly updates about new features and tips to get the the most
- out of Meilisearch.
-
-
-
-
- )
-}
-export default RightPanel
diff --git a/src/hooks/useNewsletter.js b/src/hooks/useNewsletter.js
new file mode 100644
index 00000000..a420b025
--- /dev/null
+++ b/src/hooks/useNewsletter.js
@@ -0,0 +1,57 @@
+import version from '../version/version'
+
+const PORTAL_ID = process.env.REACT_APP_HUBSPOT_PORTAL_ID
+const FORM_GUID = process.env.REACT_APP_HUBSPOT_FORM_GUID
+
+const PAGE_NAME =
+ process.env.NODE_ENV === 'development'
+ ? `Mini-dashboard (dev)`
+ : `Mini-dashboard v${version}`
+
+function getBody({ email, pageName }) {
+ return {
+ fields: [
+ {
+ objectTypeId: '0-1',
+ name: 'email',
+ value: email,
+ },
+ ],
+ context: {
+ pageName,
+ },
+ legalConsentOptions: {
+ consent: {
+ consentToProcess: true,
+ text: 'I agree to allow Meilisearch to store and process my personal data.',
+ communications: [
+ {
+ value: true,
+ subscriptionTypeId: 999,
+ text: 'I agree to receive marketing communications from Meilisearch.',
+ },
+ ],
+ },
+ },
+ }
+}
+
+export default function useNewsletter() {
+ const endpoint = `https://api.hsforms.com/submissions/v3/integration/submit/${PORTAL_ID}/${FORM_GUID}`
+
+ const subscribe = (email) =>
+ fetch(endpoint, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(
+ getBody({
+ email,
+ pageName: PAGE_NAME,
+ })
+ ),
+ })
+
+ return { subscribe }
+}