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 && ( + + )} ); }