From d2621183e24fdd5ae6a15fd1f1bbaffdd9e34530 Mon Sep 17 00:00:00 2001
From: Charis <26616127+charislam@users.noreply.github.com>
Date: Wed, 5 Jun 2024 11:04:45 -0400
Subject: [PATCH] refactor(docs): move components out of app (#25819)
---
apps/docs/features/auth/auth.client.tsx | 60 ++++
.../docs/features/data/queryClient.client.tsx | 15 +
apps/docs/features/envs/staging.client.tsx | 31 +++
.../features/telemetry/telemetry.client.tsx | 52 ++++
apps/docs/features/ui/helpers.constants.ts | 3 +
.../features/ui/helpers.scroll.client.tsx | 34 +++
apps/docs/features/ui/theme.client.tsx | 11 +
apps/docs/layouts/MainSkeleton.tsx | 7 +-
apps/docs/lib/constants.ts | 9 +-
apps/docs/pages/_app.tsx | 260 ++----------------
apps/docs/pages/index.mdx | 9 +-
11 files changed, 245 insertions(+), 246 deletions(-)
create mode 100644 apps/docs/features/auth/auth.client.tsx
create mode 100644 apps/docs/features/data/queryClient.client.tsx
create mode 100644 apps/docs/features/envs/staging.client.tsx
create mode 100644 apps/docs/features/telemetry/telemetry.client.tsx
create mode 100644 apps/docs/features/ui/helpers.constants.ts
create mode 100644 apps/docs/features/ui/helpers.scroll.client.tsx
create mode 100644 apps/docs/features/ui/theme.client.tsx
diff --git a/apps/docs/features/auth/auth.client.tsx b/apps/docs/features/auth/auth.client.tsx
new file mode 100644
index 00000000000..21f35db3aca
--- /dev/null
+++ b/apps/docs/features/auth/auth.client.tsx
@@ -0,0 +1,60 @@
+'use client'
+
+import { SessionContextProvider } from '@supabase/auth-helpers-react'
+import { createClient } from '@supabase/supabase-js'
+import { useQueryClient } from '@tanstack/react-query'
+import { AuthProvider, useConstant } from 'common'
+import { type PropsWithChildren, useCallback } from 'react'
+import { IS_PLATFORM } from '~/lib/constants'
+import { LOCAL_STORAGE_KEYS, remove } from '~/lib/storage'
+import { useOnLogout } from '~/lib/userAuth'
+
+const AuthContainerInternal = ({ children }: PropsWithChildren) => {
+ const supabase = useConstant(() =>
+ IS_PLATFORM
+ ? createClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
+ )
+ : undefined
+ )
+
+ return IS_PLATFORM ? (
+
+ {children}
+
+ ) : (
+ {children}
+ )
+}
+
+/**
+ *
+ * !!! IMPORTANT !!!
+ * Ensure data is cleared on sign out.
+ *
+ */
+const SignOutHandler = ({ children }: PropsWithChildren) => {
+ const queryClient = useQueryClient()
+
+ const cleanUp = useCallback(() => {
+ queryClient.cancelQueries()
+ queryClient.clear()
+
+ Object.keys(LOCAL_STORAGE_KEYS).forEach((key) => {
+ remove('local', LOCAL_STORAGE_KEYS[key])
+ })
+ }, [queryClient])
+
+ useOnLogout(cleanUp)
+
+ return <>{children}>
+}
+
+const AuthContainer = ({ children }: PropsWithChildren) => (
+
+ {children}
+
+)
+
+export { AuthContainer }
diff --git a/apps/docs/features/data/queryClient.client.tsx b/apps/docs/features/data/queryClient.client.tsx
new file mode 100644
index 00000000000..a346e9117b5
--- /dev/null
+++ b/apps/docs/features/data/queryClient.client.tsx
@@ -0,0 +1,15 @@
+'use client'
+
+import { QueryClientProvider as QueryClientProviderPrimitive } from '@tanstack/react-query'
+import { type PropsWithChildren } from 'react'
+import { useRootQueryClient } from '~/lib/fetch/queryClient'
+
+const QueryClientProvider = ({ children }: PropsWithChildren) => {
+ const queryClient = useRootQueryClient()
+
+ return (
+ {children}
+ )
+}
+
+export { QueryClientProvider }
diff --git a/apps/docs/features/envs/staging.client.tsx b/apps/docs/features/envs/staging.client.tsx
new file mode 100644
index 00000000000..43583cdbf2d
--- /dev/null
+++ b/apps/docs/features/envs/staging.client.tsx
@@ -0,0 +1,31 @@
+'use client'
+
+import { type PropsWithChildren, useEffect, useState } from 'react'
+import { BUILD_PREVIEW_HTML, IS_PREVIEW } from '~/lib/constants'
+
+/**
+ * Preview builds don't need to be statically generated to optimize performance.
+ * This (somewhat hacky) way of shortcutting preview builds cuts their build
+ * time and speeds up the feedback loop for previewing docs changes in Vercel.
+ *
+ * This technically breaks the Rules of Hooks to avoid an unnecessary full-app
+ * rerender in prod, but this is fine because IS_PREVIEW will never change on
+ * you within a single build.
+ */
+const ShortcutPreviewBuild = ({ children }: PropsWithChildren) => {
+ if (!BUILD_PREVIEW_HTML && IS_PREVIEW) {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const [isMounted, setIsMounted] = useState(false)
+
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ useEffect(() => {
+ setIsMounted(true)
+ }, [])
+
+ return isMounted ? children : null
+ }
+
+ return children
+}
+
+export { ShortcutPreviewBuild }
diff --git a/apps/docs/features/telemetry/telemetry.client.tsx b/apps/docs/features/telemetry/telemetry.client.tsx
new file mode 100644
index 00000000000..527328e09cc
--- /dev/null
+++ b/apps/docs/features/telemetry/telemetry.client.tsx
@@ -0,0 +1,52 @@
+'use client'
+
+import { useTelemetryProps } from 'common'
+import { usePathname } from 'next/navigation'
+import { useCallback, useEffect } from 'react'
+import { useConsent } from 'ui-patterns/ConsentToast'
+import { BASE_PATH, IS_PLATFORM } from '~/lib/constants'
+import { unauthedAllowedPost } from '~/lib/fetch/fetchWrappers'
+
+const useSendPageTelemetryWithConsent = () => {
+ const { hasAcceptedConsent } = useConsent()
+ const telemetryProps = useTelemetryProps()
+
+ const sendPageTelemetry = useCallback(
+ (route: string) => {
+ if (!(IS_PLATFORM && hasAcceptedConsent)) return
+
+ unauthedAllowedPost('/platform/telemetry/page', {
+ body: {
+ referrer: document.referrer,
+ title: document.title,
+ route: `${BASE_PATH}${route}`,
+ ga: {
+ screen_resolution: telemetryProps?.screenResolution,
+ language: telemetryProps?.language,
+ session_id: '',
+ },
+ },
+ }).catch((e) => {
+ console.error('Problem sending telemetry:', e)
+ })
+ },
+ [telemetryProps, hasAcceptedConsent]
+ )
+
+ return sendPageTelemetry
+}
+
+const PageTelemetry = () => {
+ const pathname = usePathname()
+ const sendPageTelemetry = useSendPageTelemetryWithConsent()
+
+ useEffect(() => {
+ if (pathname) {
+ sendPageTelemetry(pathname)
+ }
+ }, [pathname, sendPageTelemetry])
+
+ return null
+}
+
+export { PageTelemetry }
diff --git a/apps/docs/features/ui/helpers.constants.ts b/apps/docs/features/ui/helpers.constants.ts
new file mode 100644
index 00000000000..dbf22cc6205
--- /dev/null
+++ b/apps/docs/features/ui/helpers.constants.ts
@@ -0,0 +1,3 @@
+const DOCS_CONTENT_CONTAINER_ID = 'docs-content-container'
+
+export { DOCS_CONTENT_CONTAINER_ID }
diff --git a/apps/docs/features/ui/helpers.scroll.client.tsx b/apps/docs/features/ui/helpers.scroll.client.tsx
new file mode 100644
index 00000000000..410e922afb5
--- /dev/null
+++ b/apps/docs/features/ui/helpers.scroll.client.tsx
@@ -0,0 +1,34 @@
+'use client'
+
+import { usePathname } from 'next/navigation'
+import { useEffect } from 'react'
+import { DOCS_CONTENT_CONTAINER_ID } from './helpers.constants'
+
+const useScrollTopOnPageChange = () => {
+ const pathname = usePathname()
+
+ useEffect(() => {
+ if (document && pathname) {
+ // Don't scroll on reference pages
+ if (pathname.startsWith('/reference/')) return
+
+ const container = document.getElementById(DOCS_CONTENT_CONTAINER_ID)
+ if (container) container.scrollTop = 0
+ /**
+ * a11y works by default, so no need to specially handle it
+ */
+ }
+ }, [pathname])
+}
+
+/**
+ * Scroll the docs content container to top on page change. Can't use Next.js's
+ * native scroll restoration, because we scroll the content container, not the
+ * document.
+ */
+const ScrollRestoration = () => {
+ useScrollTopOnPageChange()
+ return null
+}
+
+export { ScrollRestoration }
diff --git a/apps/docs/features/ui/theme.client.tsx b/apps/docs/features/ui/theme.client.tsx
new file mode 100644
index 00000000000..7adf0813361
--- /dev/null
+++ b/apps/docs/features/ui/theme.client.tsx
@@ -0,0 +1,11 @@
+'use client'
+
+import { useThemeSandbox } from 'common'
+
+const ThemeSandbox = () => {
+ useThemeSandbox()
+
+ return null
+}
+
+export { ThemeSandbox }
diff --git a/apps/docs/layouts/MainSkeleton.tsx b/apps/docs/layouts/MainSkeleton.tsx
index 4b17c279fd7..18206d69af1 100644
--- a/apps/docs/layouts/MainSkeleton.tsx
+++ b/apps/docs/layouts/MainSkeleton.tsx
@@ -2,13 +2,12 @@ import { useTheme } from 'next-themes'
import Image from 'next/legacy/image'
import Link from 'next/link'
import { type CSSProperties, type PropsWithChildren, memo, useEffect } from 'react'
-
import { cn } from 'ui'
-
import Footer from '~/components/Navigation/Footer'
import HomeMenuIconPicker from '~/components/Navigation/NavigationMenu/HomeMenuIconPicker'
import NavigationMenu, { type MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import TopNavBar from '~/components/Navigation/NavigationMenu/TopNavBar'
+import { DOCS_CONTENT_CONTAINER_ID } from '~/features/ui/helpers.constants'
import { menuState, useMenuMobileOpen } from '~/hooks/useMenuState'
const levelsData = {
@@ -275,8 +274,8 @@ const Container = memo(function Container({
return (
{
- setIsMounted(true)
- }, [])
-
- return isMounted ? children : null
- }
-
- return children
-}
-
-/**
- *
- * !!! IMPORTANT !!!
- * Ensure data is cleared on sign out.
- *
- * **/
-function SignOutHandler({ children }: PropsWithChildren) {
- const queryClient = useQueryClient()
-
- const cleanUp = useCallback(() => {
- queryClient.cancelQueries()
- queryClient.clear()
-
- Object.keys(LOCAL_STORAGE_KEYS).forEach((key) => {
- remove('local', LOCAL_STORAGE_KEYS[key])
- })
- }, [queryClient])
-
- useOnLogout(cleanUp)
-
- return <>{children}>
-}
-
-function AuthContainer({ children }: PropsWithChildren) {
- const [supabase] = useState(() =>
- IS_PLATFORM
- ? createClient(
- process.env.NEXT_PUBLIC_SUPABASE_URL,
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
- )
- : undefined
- )
-
- return IS_PLATFORM ? (
-
- {children}
-
- ) : (
-
{children}
- )
-}
+import type { AppPropsWithLayout } from '~/types'
function MyApp({ Component, pageProps }: AppPropsWithLayout) {
- const router = useRouter()
- const telemetryProps = useTelemetryProps()
- const { consentValue, hasAcceptedConsent } = useConsent()
- const queryClient = useRootQueryClient()
-
- useThemeSandbox()
-
- const handlePageTelemetry = useCallback(
- (route: string) => {
- if (IS_PLATFORM) {
- unauthedAllowedPost('/platform/telemetry/page', {
- body: {
- referrer: document.referrer,
- title: document.title,
- route,
- ga: {
- screen_resolution: telemetryProps?.screenResolution,
- language: telemetryProps?.language,
- session_id: '',
- },
- },
- }).catch((e) => {
- console.error('Problem sending telemetry:', e)
- })
- }
- },
- [telemetryProps]
- )
-
- useEffect(() => {
- function handleRouteChange(url: string) {
- /*
- * handle telemetry
- */
- if (hasAcceptedConsent) handlePageTelemetry(url)
- /*
- * handle "scroll to top" behaviour on route change
- */
- if (document) {
- // do not scroll to top for reference docs
- if (!url.includes('reference/')) {
- // scroll container div to top
- const container = document.getElementById('docs-content-container')
- // check container exists (only avail on new docs)
- if (container) container.scrollTop = 0
- }
- }
- }
-
- // Listen for page changes after a navigation or when the query changes
- router.events.on('routeChangeComplete', handleRouteChange)
- return () => {
- router.events.off('routeChangeComplete', handleRouteChange)
- }
- }, [router, handlePageTelemetry, consentValue, hasAcceptedConsent])
-
- /**
- * Save/restore scroll position when reloading or navigating back/forward.
- *
- * Required since scroll happens within a sub-container, not the page root.
- */
- useEffect(() => {
- const storageKey = 'scroll-position'
-
- const container = document.getElementById('docs-content-container')
- if (!container) {
- return
- }
-
- const previousScroll = Number(sessionStorage.getItem(storageKey))
- const [entry] = window.performance.getEntriesByType('navigation')
-
- // Only restore scroll position on reload and back/forward events
- if (
- previousScroll &&
- entry &&
- isPerformanceNavigationTiming(entry) &&
- ['reload', 'back_forward'].includes(entry.type)
- ) {
- container.scrollTop = previousScroll
- }
-
- const handler = () => {
- // Scroll stored in session storage, so only persisted per tab
- sessionStorage.setItem(storageKey, container.scrollTop.toString())
- }
-
- window.addEventListener('beforeunload', handler)
-
- return () => window.removeEventListener('beforeunload', handler)
- }, [router])
-
- useEffect(() => {
- if (!hasAcceptedConsent) return
-
- /**
- * Send page telemetry on first page load
- */
- if (router.isReady) {
- handlePageTelemetry(router.basePath + router.asPath)
- }
- }, [router, handlePageTelemetry, consentValue, hasAcceptedConsent])
-
- /**
- * Reference docs use `history.pushState()` to jump to
- * sub-sections without causing a re-render.
- *
- * We need to the below handler to manually force a re-render
- * when navigating away from, then back to reference docs
- */
- useEffect(() => {
- function handler() {
- router.replace(window.location.href)
- }
-
- window.addEventListener('popstate', handler)
-
- return () => {
- window.removeEventListener('popstate', handler)
- }
- }, [router])
-
return (
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
)
}
-/**
- * Type guard that checks if a performance entry is a
- * `PerformanceNavigationTiming`.
- */
-function isPerformanceNavigationTiming(
- entry: PerformanceEntry
-): entry is PerformanceNavigationTiming {
- return entry.entryType === 'navigation'
-}
-
export default MyApp
diff --git a/apps/docs/pages/index.mdx b/apps/docs/pages/index.mdx
index a26bd85cb18..5b912d11238 100644
--- a/apps/docs/pages/index.mdx
+++ b/apps/docs/pages/index.mdx
@@ -86,10 +86,9 @@ export const meta = {
{migrationGuides.map((product) => {
return (
- -
+
-
@@ -116,10 +115,9 @@ export const meta = {
{additionalResources.map((product) => {
return (
- -
+
-
@@ -160,10 +158,9 @@ export const meta = {
{selfHostingOptions.map((product) => {
return (
- -
+
-
} background={true} showIconBg={true} showLink={false}>