diff --git a/.example.env b/.example.env
new file mode 100644
index 000000000..58db2efc3
--- /dev/null
+++ b/.example.env
@@ -0,0 +1,7 @@
+# Find these values in netlify if you are missing them
+# https://app.netlify.com/sites/docs-optimism/configuration/env#content
+
+NEXT_PUBLIC_KAPA_WEBSITE_ID=
+NEXT_PUBLIC_GROWTHBOOK_API_HOST=
+NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY=
+NEXT_PUBLIC_GA_ID=
diff --git a/components/AskAIButton.tsx b/components/AskAIButton.tsx
new file mode 100644
index 000000000..7ce0372c4
--- /dev/null
+++ b/components/AskAIButton.tsx
@@ -0,0 +1,19 @@
+import { RiSparkling2Fill } from '@remixicon/react';
+import { useFeature } from '@growthbook/growthbook-react';
+
+const AskAIButton = () => {
+ const enableDocsAIWidget = useFeature('enable_docs_ai_widget').on;
+
+ if (!enableDocsAIWidget) {
+ return null;
+ }
+
+ return (
+
+ );
+};
+
+export { AskAIButton };
diff --git a/lib/growthbook.ts b/lib/growthbook.ts
new file mode 100644
index 000000000..fb3c8d6fa
--- /dev/null
+++ b/lib/growthbook.ts
@@ -0,0 +1,17 @@
+// lib/growthbook.ts
+import { GrowthBook } from '@growthbook/growthbook-react';
+
+if (!process.env.NEXT_PUBLIC_GROWTHBOOK_API_HOST || !process.env.NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY) {
+ throw new Error('NEXT_PUBLIC_GROWTHBOOK_API_HOST and NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY must be set');
+}
+
+export const growthbook = new GrowthBook({
+ apiHost: process.env.NEXT_PUBLIC_GROWTHBOOK_API_HOST,
+ clientKey: process.env.NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY
+});
+
+try {
+ growthbook.init();
+} catch (error) {
+ console.error('Error initializing GrowthBook', error);
+}
diff --git a/package.json b/package.json
index 155f23b90..03feef0b6 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"@eth-optimism/contracts-ts": "^0.17.0",
"@eth-optimism/tokenlist": "^9.0.9",
"@feelback/react": "^0.3.4",
+ "@growthbook/growthbook-react": "^1.3.1",
"@headlessui/react": "^2.1.8",
"@remixicon/react": "^4.6.0",
"algoliasearch": "^4.23.3",
diff --git a/pages/_app.tsx b/pages/_app.tsx
index c78d11add..b53a8a990 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -1,45 +1,47 @@
-import '../styles/global.css'
+import '../styles/global.css';
-import { useEffect, useState } from 'react'
-import { useRouter } from 'next/router'
-import * as gtag from '../utils/gtag'
-import * as aa from "search-insights"
-import AlgoliaContext from '@/utils/contexts/AlgoliaContext'
-import ScrollDispatcher from '@/components/ScrollDispatcher'
+import { useEffect, useState } from 'react';
+import { useRouter } from 'next/router';
+import * as gtag from '../utils/gtag';
+import * as aa from 'search-insights';
+import AlgoliaContext from '@/utils/contexts/AlgoliaContext';
+import ScrollDispatcher from '@/components/ScrollDispatcher';
+import { CustomGrowthBookProvider } from '../providers/GrowthbookProvider';
export default function App({ Component, pageProps }) {
const [queryID, setQueryID] = useState(null);
const [objectID, setObjectID] = useState(null);
- const router = useRouter()
+ const router = useRouter();
useEffect(() => {
const handleRouteChange = (url) => {
- gtag.pageview(url)
- }
- router.events.on('routeChangeComplete', handleRouteChange)
+ gtag.pageview(url);
+ };
+ router.events.on('routeChangeComplete', handleRouteChange);
return () => {
- router.events.off('routeChangeComplete', handleRouteChange)
- }
- }, [router.events])
+ router.events.off('routeChangeComplete', handleRouteChange);
+ };
+ }, [router.events]);
aa.default('init', {
- appId: "JCF9BUJTB9",
- apiKey: "cc766a73d4b0004e3059677de49297a2"
- })
+ appId: 'JCF9BUJTB9',
+ apiKey: 'cc766a73d4b0004e3059677de49297a2'
+ });
return (
-
-
-
-
-
- )
+
+
+
+
+
+
+
+ );
}
-
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d89a9de2d..f5b4a1968 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -28,6 +28,9 @@ importers:
'@feelback/react':
specifier: ^0.3.4
version: 0.3.4(react@18.2.0)
+ '@growthbook/growthbook-react':
+ specifier: ^1.3.1
+ version: 1.3.1(react@18.2.0)
'@headlessui/react':
specifier: ^2.1.8
version: 2.1.8(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -604,6 +607,16 @@ packages:
'@floating-ui/utils@0.2.8':
resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==}
+ '@growthbook/growthbook-react@1.3.1':
+ resolution: {integrity: sha512-1ED2dG3lXrjVmxR2arOHmQEuasgcppaslgyxQxlykWJZ/UL5JpH59j6avOYgZzLlHkDfN1+Ic4X6Y/Vel4vK2g==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ react: ^16.8.0-0 || ^17.0.0-0 || ^18.0.0-0
+
+ '@growthbook/growthbook@1.3.1':
+ resolution: {integrity: sha512-ewtwq6+86rRKwcYUXEmBVR1JuiEIYZhxow/Z52qyAxJwEHdXmpS4Yk8sVeVD9bphCwE2r0zuifxFkBxmnIL4Mg==}
+ engines: {node: '>=10'}
+
'@headlessui/react@1.7.17':
resolution: {integrity: sha512-4am+tzvkqDSSgiwrsEpGWqgGo9dz8qU5M3znCkC4PgkpY4HcCZzEDEvozltGGGHIKl9jbXbZPSH5TWn4sWJdow==}
engines: {node: '>=10'}
@@ -1631,6 +1644,10 @@ packages:
dom-accessibility-api@0.5.16:
resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
+ dom-mutator@0.6.0:
+ resolution: {integrity: sha512-iCt9o0aYfXMUkz/43ZOAUFQYotjGB+GNbYJiJdz4TgXkyToXbbRy5S6FbTp72lRBtfpUMwEc1KmpFEU4CZeoNg==}
+ engines: {node: '>=10'}
+
dompurify@3.0.6:
resolution: {integrity: sha512-ilkD8YEnnGh1zJ240uJsW7AzE+2qpbOUYjacomn3AvJ6J4JhKGSZ2nh4wUIXPZrEPppaCLx5jFe8T89Rk8tQ7w==}
@@ -4545,6 +4562,15 @@ snapshots:
'@floating-ui/utils@0.2.8': {}
+ '@growthbook/growthbook-react@1.3.1(react@18.2.0)':
+ dependencies:
+ '@growthbook/growthbook': 1.3.1
+ react: 18.2.0
+
+ '@growthbook/growthbook@1.3.1':
+ dependencies:
+ dom-mutator: 0.6.0
+
'@headlessui/react@1.7.17(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
client-only: 0.0.1
@@ -5665,6 +5691,8 @@ snapshots:
dom-accessibility-api@0.5.16: {}
+ dom-mutator@0.6.0: {}
+
dompurify@3.0.6: {}
dot-case@3.0.4:
diff --git a/providers/GrowthbookProvider.tsx b/providers/GrowthbookProvider.tsx
new file mode 100644
index 000000000..e20053b3e
--- /dev/null
+++ b/providers/GrowthbookProvider.tsx
@@ -0,0 +1,7 @@
+// components/GrowthBookProvider.tsx
+import { GrowthBookProvider } from '@growthbook/growthbook-react';
+import { growthbook } from '../lib/growthbook';
+
+export function CustomGrowthBookProvider({ children }: { children: React.ReactNode }) {
+ return {children};
+}
diff --git a/theme.config.tsx b/theme.config.tsx
index 59112e275..a7006267e 100644
--- a/theme.config.tsx
+++ b/theme.config.tsx
@@ -5,7 +5,8 @@ import { useConfig } from 'nextra-theme-docs';
import { FeelbackYesNo, PRESET_LIKE_DISLIKE } from '@feelback/react';
import '@feelback/react/styles/feelback.css';
import { Search } from './components/Search';
-import { RiSparkling2Fill } from '@remixicon/react';
+import { AskAIButton } from './components/AskAIButton';
+import { useFeature } from '@growthbook/growthbook-react';
const config: DocsThemeConfig = {
logo: (
@@ -31,12 +32,7 @@ const config: DocsThemeConfig = {
component: Search
},
navbar: {
- extraContent: (
-
- )
+ extraContent: AskAIButton
},
docsRepositoryBase: 'https://github.com/ethereum-optimism/docs/blob/main/',
footer: {
@@ -121,6 +117,7 @@ const config: DocsThemeConfig = {
const { asPath, defaultLocale, locale } = useRouter();
const { frontMatter } = useConfig();
const url = 'https://docs.optimism.io' + (defaultLocale === locale ? asPath : `/${locale}${asPath}`);
+ const enableDocsAIWidget = useFeature('enable_docs_ai_widget').on;
return (
<>
@@ -133,20 +130,23 @@ const config: DocsThemeConfig = {
-
+ {enableDocsAIWidget && (
+
+ )}
>
);
}