Skip to content

Commit 21dd633

Browse files
committed
Merge remote-tracking branch 'tannerlinsley/master' into alpha
# Conflicts: # src/reactjs/tests/useIsFetching.test.tsx # src/reactjs/useIsFetching.ts
2 parents ba78256 + 31bcaf1 commit 21dd633

File tree

20 files changed

+524
-243
lines changed

20 files changed

+524
-243
lines changed

docs/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"body-scroll-lock": "^3.1.5",
2929
"classnames": "^2.2.6",
3030
"copy-to-clipboard": "^3.3.1",
31+
"country-emoji": "^1.5.6",
3132
"date-fns": "^2.16.1",
3233
"docsearch.js": "^2.6.3",
3334
"framer-motion": "^1.11.1",

docs/public/images/bytes-logo.png

91.8 KB
Loading

docs/src/components/BytesForm.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React from 'react'
2+
import useBytesSubmit from './useBytesSubmit'
3+
4+
export default function BytesForm() {
5+
const { state, handleSubmit, error } = useBytesSubmit()
6+
if (state === 'submitted') {
7+
return (
8+
<p>Success! Please, check your email to confirm your subscription.</p>
9+
)
10+
}
11+
return (
12+
<form onSubmit={handleSubmit}>
13+
<div data-element="fields" className="grid relative">
14+
<figure
15+
className="absolute right-0"
16+
style={{ bottom: '72px', right: '-10px' }}
17+
>
18+
<img
19+
height={40}
20+
width={40}
21+
src="/images/bytes-logo.png"
22+
alt="Bytes"
23+
/>
24+
</figure>
25+
<input
26+
className="border rounded p-2 mb-2 w-full"
27+
name="email_address"
28+
placeholder="Your email address"
29+
type="email"
30+
required=""
31+
/>
32+
<button
33+
type="submit"
34+
className="mb-4 border rounded bg-coral border-none text-white p-2"
35+
>
36+
{state !== 'loading' ? (
37+
<span>Subscribe</span>
38+
) : (
39+
<span>Loading...</span>
40+
)}
41+
</button>
42+
</div>
43+
<p className="text-gray-400 text-xs">
44+
No spam. Unsubscribe at <em>any</em> time.
45+
</p>
46+
{error && <p className="text-red-600">{error}</p>}
47+
</form>
48+
)
49+
}

docs/src/components/Footer.js

Lines changed: 9 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import * as React from 'react'
22
import Link from 'next/link'
33
import CarbonAds from './CarbonAds'
4+
import BytesForm from './BytesForm'
5+
46
export const Footer = props => {
57
return (
68
<div className="bg-gray-50 border-t border-gray-200">
@@ -91,56 +93,16 @@ export const Footer = props => {
9193
</div>
9294
<div className="mt-8 xl:mt-0">
9395
<h4 className="text-sm leading-5 font-semibold tracking-wider text-gray-400 uppercase">
94-
Subscribe to our newsletter
96+
Subscribe to Bytes
9597
</h4>
9698
<p className="mt-4 text-gray-500 text-base leading-6 mb-4">
97-
The latest TanStack news, articles, and resources, sent to your
98-
inbox.
99+
The{' '}
100+
<a href="https://bytes.dev/?r=tanstack">
101+
best JavaScript newsletter!
102+
</a>{' '}
103+
Delivered every Monday to over 76,000 devs.
99104
</p>
100-
<form
101-
action="https://app.convertkit.com/forms/1513638/subscriptions"
102-
className=""
103-
method="post"
104-
data-sv-form="1513638"
105-
data-uid="4fc050bc50"
106-
data-format="inline"
107-
data-version="5"
108-
data-options='{"settings":{"after_subscribe":{"action":"message","success_message":"Success! Please, check your email to confirm your subscription.","redirect_url":""},"modal":{"trigger":null,"scroll_percentage":null,"timer":null,"devices":null,"show_once_every":null},"recaptcha":{"enabled":false},"slide_in":{"display_in":null,"trigger":null,"scroll_percentage":null,"timer":null,"devices":null,"show_once_every":null}}}'
109-
>
110-
<ul
111-
className="formkit-alert formkit-alert-error"
112-
data-element="errors"
113-
data-group="alert"
114-
/>
115-
116-
<div
117-
data-element="fields"
118-
className="seva-fields grid grid-cols-3 gap-2 max-w-lg"
119-
>
120-
<input
121-
className="formkit-input border rounded p-2 mb-4 w-full col-span-2"
122-
name="email_address"
123-
placeholder="Your email address"
124-
type="text"
125-
required=""
126-
/>
127-
<button
128-
data-element="submit"
129-
className="formkit-submit mb-4 border rounded bg-coral border-none text-white"
130-
>
131-
<span>Subscribe</span>
132-
</button>
133-
</div>
134-
<div
135-
data-element="guarantee"
136-
className="formkit-guarantee text-gray-400 text-xs mt-4"
137-
>
138-
<p>I won't send you spam.</p>
139-
<p>
140-
Unsubscribe at <em>any</em> time.
141-
</p>
142-
</div>
143-
</form>
105+
<BytesForm />
144106
</div>
145107
</div>
146108
<div className="mt-8 border-t border-gray-200 pt-8 md:flex md:items-center md:justify-between">

docs/src/components/LayoutDocs.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { Seo } from './Seo'
2020
import MDXComponents from './MDXComponents'
2121
import Head from 'next/head'
2222
import { getManifest } from 'manifests/getManifest'
23+
import BytesForm from './BytesForm'
2324

2425
const getSlugAndTag = path => {
2526
const parts = path.split('/')
@@ -111,6 +112,31 @@ export const LayoutDocs = props => {
111112
On this page
112113
</h4>
113114
<Toc title={props.meta.title} />
115+
<div className="mt-12">
116+
<h4 className="font-semibold uppercase text-sm mb-2 mt-2 text-gray-500">
117+
Want to skip the docs?
118+
</h4>
119+
<p className='text-sm'>
120+
Fast track your learning and {' '}
121+
<a
122+
href="https://ui.dev/checkout/react-query?from=tanstack"
123+
className="text-blue-600 font-semibold transition-colors duration-150 ease-out"
124+
>
125+
take the offical course ↗️
126+
</a>
127+
</p>
128+
</div>
129+
<div className="mt-12 relative">
130+
<h4 className="font-semibold uppercase text-sm mb-2 mt-2 text-gray-500">
131+
Subscribe to Bytes
132+
</h4>
133+
<p className="mt-4 text-sm leading-6 mb-4">
134+
The best JavaScript newsletter! Delivered every
135+
Monday to over 76,000 devs.
136+
</p>
137+
<BytesForm />
138+
139+
</div>
114140
</div>
115141
</div>
116142
)}

docs/src/components/Nav.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import Link from 'next/link'
33
import logoSrc from '../images/logo.svg'
44
import { siteConfig } from 'siteConfig'
55
import { Search } from './Search'
6+
import { PPPBanner } from './PPPBanner'
67

78
export const Nav = () => (
89
<div className="bg-white border-b border-gray-200">
10+
<PPPBanner />
911
<div className="container mx-auto">
1012
<div className="flex flex-wrap items-center">
1113
<div className="w-60 flex items-center h-16 pt-4 md:pt-0">

docs/src/components/PPPBanner.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import * as React from 'react'
2+
import { flag } from 'country-emoji'
3+
import useLocalStorage from './useLocalStorage'
4+
import { AnimatePresence, motion } from 'framer-motion'
5+
import { IoIosClose } from 'react-icons/io'
6+
7+
function useClientOnlyRender() {
8+
const [rendered, setRendered] = React.useState(false)
9+
React.useEffect(() => {
10+
setRendered(true)
11+
}, [])
12+
return rendered
13+
}
14+
export function PPPBanner() {
15+
const [hidden, setHidden] = useLocalStorage('pppbanner-hidden', false)
16+
const [data, setData] = useLocalStorage('pppbanner-data', null)
17+
18+
React.useEffect(() => {
19+
// This function has CORS configured to allow
20+
// react-query.tanstack.com and tanstack.com
21+
if (!data) {
22+
fetch('https://ui.dev/api/ppp-discount')
23+
.then(res => res.json())
24+
.then(res => {
25+
if (res?.code) {
26+
setData(res)
27+
}
28+
})
29+
}
30+
}, [data, setData])
31+
32+
if (!useClientOnlyRender()) {
33+
return null
34+
}
35+
36+
return (
37+
<AnimatePresence initial={false}>
38+
{data && !hidden && (
39+
<motion.div
40+
initial={{ opacity: 0, height: 0 }}
41+
animate={{ opacity: 1, height: 'auto' }}
42+
exit={{ opacity: 0, height: 0 }}
43+
className="w-full bg-coral text-white text-center py-2 relative flex items-center justify-center"
44+
>
45+
<p>
46+
{flag(data.code)} We noticed you're in{' '}
47+
<strong>{data.country}</strong>. Get{' '}
48+
<strong>{data.discount * 100}% off</strong> the Official React Query
49+
Course with code{' '}
50+
<a
51+
className="underline cursor-pointer"
52+
href={`/checkout/react-query?from=tanstack&coupon_code=${data.coupon}`}
53+
>
54+
<strong>{data.coupon}</strong>
55+
</a>
56+
.
57+
</p>
58+
<button
59+
onClick={() => setHidden(true)}
60+
className="absolute right-0"
61+
aria-label="Hide Banner"
62+
>
63+
<IoIosClose size={30} className="text-white" />
64+
</button>
65+
</motion.div>
66+
)}
67+
</AnimatePresence>
68+
)
69+
}

docs/src/components/useBytesSubmit.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { useState } from 'react';
2+
3+
function sendBytesOptIn({ email, influencer, source, referral }) {
4+
return fetch(`https://bytes.dev/api/bytes-optin-cors`, {
5+
method: 'POST',
6+
body: JSON.stringify({ email, influencer, source, referral }),
7+
headers: {
8+
Accept: 'application/json',
9+
'Content-Type': 'application/json',
10+
},
11+
}).then((res) => res.json())
12+
}
13+
14+
export default function useBytesSubmit() {
15+
const [state, setState] = useState("initial");
16+
const [error, setError] = useState(null);
17+
18+
const handleSubmit = (e) => {
19+
e.preventDefault();
20+
const email = e.target.email_address.value;
21+
setState("loading");
22+
sendBytesOptIn({ email, influencer: "tanstack" })
23+
.then(() => {
24+
setState("submitted");
25+
})
26+
.catch((err) => {
27+
setError(err);
28+
});
29+
};
30+
31+
return { handleSubmit, state, error };
32+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import * as React from 'react'
2+
export default function useLocalStorage(key, initialValue) {
3+
// State to store our value
4+
// Pass initial state function to useState so logic is only executed once
5+
const [storedValue, setStoredValue] = React.useState(() => {
6+
if (typeof window !== 'undefined') {
7+
const item = window.localStorage.getItem(key)
8+
try {
9+
// Get from local storage by key
10+
// Parse stored json or if none return initialValue
11+
return item ? JSON.parse(item) : initialValue
12+
} catch (error) {
13+
return item
14+
}
15+
}
16+
return initialValue
17+
})
18+
// Return a wrapped version of useState's setter function that ...
19+
// ... persists the new value to localStorage.
20+
const setValue = React.useCallback(
21+
value => {
22+
try {
23+
// Allow value to be a function so we have same API as useState
24+
// Save state
25+
setStoredValue(newStoredValue => {
26+
const valueToStore =
27+
value instanceof Function ? value(newStoredValue) : value
28+
window.localStorage.setItem(key, JSON.stringify(valueToStore))
29+
return valueToStore
30+
})
31+
// Save to local storage
32+
} catch (error) {
33+
// A more advanced implementation would handle the error case
34+
}
35+
},
36+
[key]
37+
)
38+
return [storedValue, setValue]
39+
}

docs/src/pages/_app.js

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ function loadScript(src, attrs = {}) {
1818
function MyApp({ Component, pageProps }) {
1919
React.useEffect(() => {
2020
loadScript('https://buttons.github.io/buttons.js')
21-
loadScript('https://tanstack.ck.page/e394781e7a/index.js', {
22-
'data-uid': 'e394781e7a',
23-
})
2421
}, [])
2522

2623
return (
@@ -30,25 +27,12 @@ function MyApp({ Component, pageProps }) {
3027
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
3128
rel="stylesheet"
3229
/>
33-
<style
34-
dangerouslySetInnerHTML={{
35-
__html: `
36-
@media (max-width: 390px) {
37-
.formkit-slide-in {
38-
display: none;
39-
}
40-
}
41-
@media (max-height: 740px) {
42-
.formkit-slide-in {
43-
display: none;
44-
}
45-
}
46-
`,
47-
}}
48-
/>
4930
</Head>
5031
{/* eslint-disable-next-line jsx-a11y/alt-text */}
51-
<img src="https://static.scarf.sh/a.png?x-pxid=c03d3ddd-b47e-4e26-a9b2-9df68b2ac970" />
32+
<img
33+
src="https://static.scarf.sh/a.png?x-pxid=c03d3ddd-b47e-4e26-a9b2-9df68b2ac970"
34+
className="h-0"
35+
/>
5236
<SearchProvider>
5337
<Component {...pageProps} />
5438
</SearchProvider>

0 commit comments

Comments
 (0)