diff --git a/docs/package.json b/docs/package.json index bfe893d997..9d58c9dfa8 100644 --- a/docs/package.json +++ b/docs/package.json @@ -28,6 +28,7 @@ "body-scroll-lock": "^3.1.5", "classnames": "^2.2.6", "copy-to-clipboard": "^3.3.1", + "country-emoji": "^1.5.6", "date-fns": "^2.16.1", "docsearch.js": "^2.6.3", "framer-motion": "^1.11.1", diff --git a/docs/public/images/bytes-logo.png b/docs/public/images/bytes-logo.png new file mode 100644 index 0000000000..26ca9143ce Binary files /dev/null and b/docs/public/images/bytes-logo.png differ diff --git a/docs/src/components/BytesForm.js b/docs/src/components/BytesForm.js new file mode 100644 index 0000000000..168fcf975f --- /dev/null +++ b/docs/src/components/BytesForm.js @@ -0,0 +1,49 @@ +import React from 'react' +import useBytesSubmit from './useBytesSubmit' + +export default function BytesForm() { + const { state, handleSubmit, error } = useBytesSubmit() + if (state === 'submitted') { + return ( +

Success! Please, check your email to confirm your subscription.

+ ) + } + return ( +
+
+
+ Bytes +
+ + +
+

+ No spam. Unsubscribe at any time. +

+ {error &&

{error}

} +
+ ) +} diff --git a/docs/src/components/Footer.js b/docs/src/components/Footer.js index 67b2851825..1beec91d4b 100644 --- a/docs/src/components/Footer.js +++ b/docs/src/components/Footer.js @@ -1,6 +1,8 @@ import * as React from 'react' import Link from 'next/link' import CarbonAds from './CarbonAds' +import BytesForm from './BytesForm' + export const Footer = props => { return (
@@ -91,56 +93,16 @@ export const Footer = props => {

- Subscribe to our newsletter + Subscribe to Bytes

- The latest TanStack news, articles, and resources, sent to your - inbox. + The{' '} + + best JavaScript newsletter! + {' '} + Delivered every Monday to over 76,000 devs.

-
-
diff --git a/docs/src/components/LayoutDocs.js b/docs/src/components/LayoutDocs.js index d890d5d940..66a529d1d4 100644 --- a/docs/src/components/LayoutDocs.js +++ b/docs/src/components/LayoutDocs.js @@ -20,6 +20,7 @@ import { Seo } from './Seo' import MDXComponents from './MDXComponents' import Head from 'next/head' import { getManifest } from 'manifests/getManifest' +import BytesForm from './BytesForm' const getSlugAndTag = path => { const parts = path.split('/') @@ -111,6 +112,31 @@ export const LayoutDocs = props => { On this page +
+

+ Want to skip the docs? +

+

+ Fast track your learning and {' '} + + take the offical course ↗️ + +

+
+
+

+ Subscribe to Bytes +

+

+ The best JavaScript newsletter! Delivered every + Monday to over 76,000 devs. +

+ + +
)} diff --git a/docs/src/components/Nav.js b/docs/src/components/Nav.js index a4a9b57033..740ca1625a 100644 --- a/docs/src/components/Nav.js +++ b/docs/src/components/Nav.js @@ -3,9 +3,11 @@ import Link from 'next/link' import logoSrc from '../images/logo.svg' import { siteConfig } from 'siteConfig' import { Search } from './Search' +import { PPPBanner } from './PPPBanner' export const Nav = () => (
+
diff --git a/docs/src/components/PPPBanner.js b/docs/src/components/PPPBanner.js new file mode 100644 index 0000000000..febf2b7698 --- /dev/null +++ b/docs/src/components/PPPBanner.js @@ -0,0 +1,69 @@ +import * as React from 'react' +import { flag } from 'country-emoji' +import useLocalStorage from './useLocalStorage' +import { AnimatePresence, motion } from 'framer-motion' +import { IoIosClose } from 'react-icons/io' + +function useClientOnlyRender() { + const [rendered, setRendered] = React.useState(false) + React.useEffect(() => { + setRendered(true) + }, []) + return rendered +} +export function PPPBanner() { + const [hidden, setHidden] = useLocalStorage('pppbanner-hidden', false) + const [data, setData] = useLocalStorage('pppbanner-data', null) + + React.useEffect(() => { + // This function has CORS configured to allow + // react-query.tanstack.com and tanstack.com + if (!data) { + fetch('https://ui.dev/api/ppp-discount') + .then(res => res.json()) + .then(res => { + if (res?.code) { + setData(res) + } + }) + } + }, [data, setData]) + + if (!useClientOnlyRender()) { + return null + } + + return ( + + {data && !hidden && ( + +

+ {flag(data.code)} We noticed you're in{' '} + {data.country}. Get{' '} + {data.discount * 100}% off the Official React Query + Course with code{' '} + + {data.coupon} + + . +

+ +
+ )} +
+ ) +} diff --git a/docs/src/components/useBytesSubmit.js b/docs/src/components/useBytesSubmit.js new file mode 100644 index 0000000000..954dff7468 --- /dev/null +++ b/docs/src/components/useBytesSubmit.js @@ -0,0 +1,32 @@ +import { useState } from 'react'; + +function sendBytesOptIn({ email, influencer, source, referral }) { + return fetch(`https://bytes.dev/api/bytes-optin-cors`, { + method: 'POST', + body: JSON.stringify({ email, influencer, source, referral }), + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + }).then((res) => res.json()) +} + +export default function useBytesSubmit() { + const [state, setState] = useState("initial"); + const [error, setError] = useState(null); + + const handleSubmit = (e) => { + e.preventDefault(); + const email = e.target.email_address.value; + setState("loading"); + sendBytesOptIn({ email, influencer: "tanstack" }) + .then(() => { + setState("submitted"); + }) + .catch((err) => { + setError(err); + }); + }; + + return { handleSubmit, state, error }; +} \ No newline at end of file diff --git a/docs/src/components/useLocalStorage.js b/docs/src/components/useLocalStorage.js new file mode 100644 index 0000000000..e861f6337e --- /dev/null +++ b/docs/src/components/useLocalStorage.js @@ -0,0 +1,39 @@ +import * as React from 'react' +export default function useLocalStorage(key, initialValue) { + // State to store our value + // Pass initial state function to useState so logic is only executed once + const [storedValue, setStoredValue] = React.useState(() => { + if (typeof window !== 'undefined') { + const item = window.localStorage.getItem(key) + try { + // Get from local storage by key + // Parse stored json or if none return initialValue + return item ? JSON.parse(item) : initialValue + } catch (error) { + return item + } + } + return initialValue + }) + // Return a wrapped version of useState's setter function that ... + // ... persists the new value to localStorage. + const setValue = React.useCallback( + value => { + try { + // Allow value to be a function so we have same API as useState + // Save state + setStoredValue(newStoredValue => { + const valueToStore = + value instanceof Function ? value(newStoredValue) : value + window.localStorage.setItem(key, JSON.stringify(valueToStore)) + return valueToStore + }) + // Save to local storage + } catch (error) { + // A more advanced implementation would handle the error case + } + }, + [key] + ) + return [storedValue, setValue] +} diff --git a/docs/src/pages/_app.js b/docs/src/pages/_app.js index 4683362435..615ae08403 100644 --- a/docs/src/pages/_app.js +++ b/docs/src/pages/_app.js @@ -18,9 +18,6 @@ function loadScript(src, attrs = {}) { function MyApp({ Component, pageProps }) { React.useEffect(() => { loadScript('https://buttons.github.io/buttons.js') - loadScript('https://tanstack.ck.page/e394781e7a/index.js', { - 'data-uid': 'e394781e7a', - }) }, []) return ( @@ -30,25 +27,12 @@ function MyApp({ Component, pageProps }) { href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" /> -