Skip to content

Commit bce4866

Browse files
syncing all local edits
1 parent f50ef16 commit bce4866

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+22313
-10690
lines changed

.eslintrc.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module.exports = {
2+
root: true,
3+
env: {
4+
browser: true,
5+
amd: true,
6+
node: true,
7+
es6: true,
8+
},
9+
extends: ['eslint:recommended', 'plugin:prettier/recommended', 'next', 'next/core-web-vitals'],
10+
rules: {
11+
'prettier/prettier': 'error',
12+
'react/react-in-jsx-scope': 'off',
13+
'react/prop-types': 0,
14+
'no-unused-vars': 0,
15+
'react/no-unescaped-entities': 0,
16+
},
17+
}

.eslintrc.json

Lines changed: 0 additions & 3 deletions
This file was deleted.

components/Card.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import Image from './Image'
2+
import Link from './Link'
3+
4+
const Card = ({ title, description, imgSrc, href }) => (
5+
<div className="md p-4 md:w-1/2" style={{ maxWidth: '544px' }}>
6+
<div
7+
className={`${
8+
imgSrc && 'h-full'
9+
} overflow-hidden rounded-md border-2 border-gray-200 border-opacity-60 dark:border-gray-700`}
10+
>
11+
{imgSrc &&
12+
(href ? (
13+
<Link href={href} aria-label={`Link to ${title}`}>
14+
<Image
15+
alt={title}
16+
src={imgSrc}
17+
className="object-cover object-center md:h-36 lg:h-48"
18+
width={544}
19+
height={306}
20+
/>
21+
</Link>
22+
) : (
23+
<Image
24+
alt={title}
25+
src={imgSrc}
26+
className="object-cover object-center md:h-36 lg:h-48"
27+
width={544}
28+
height={306}
29+
/>
30+
))}
31+
<div className="p-6">
32+
<h2 className="mb-3 text-2xl font-bold leading-8 tracking-tight">
33+
{href ? (
34+
<Link href={href} aria-label={`Link to ${title}`}>
35+
{title}
36+
</Link>
37+
) : (
38+
title
39+
)}
40+
</h2>
41+
<p className="prose mb-3 max-w-none text-gray-500 dark:text-gray-400">{description}</p>
42+
{href && (
43+
<Link
44+
href={href}
45+
className="text-base font-medium leading-6 text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
46+
aria-label={`Link to ${title}`}
47+
>
48+
Learn more &rarr;
49+
</Link>
50+
)}
51+
</div>
52+
</div>
53+
</div>
54+
)
55+
56+
export default Card

components/ClientReload.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { useEffect } from 'react'
2+
import Router from 'next/router'
3+
4+
/**
5+
* Client-side complement to next-remote-watch
6+
* Re-triggers getStaticProps when watched mdx files change
7+
*
8+
*/
9+
export const ClientReload = () => {
10+
// Exclude socket.io from prod bundle
11+
useEffect(() => {
12+
import('socket.io-client').then((module) => {
13+
const socket = module.io()
14+
socket.on('reload', (data) => {
15+
Router.replace(Router.asPath, undefined, {
16+
scroll: false,
17+
})
18+
})
19+
})
20+
}, [])
21+
22+
return null
23+
}

components/LayoutWrapper.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import siteMetadata from '@/data/siteMetadata'
2+
import headerNavLinks from '@/data/headerNavLinks'
3+
import Logo from '@/data/logo.svg'
4+
import Link from './Link'
5+
import SectionContainer from './SectionContainer'
6+
import Footer from './Footer'
7+
import MobileNav from './MobileNav'
8+
import ThemeSwitch from './ThemeSwitch'
9+
10+
const LayoutWrapper = ({ children }) => {
11+
return (
12+
<SectionContainer>
13+
<div className="flex h-screen flex-col justify-between">
14+
<header className="flex items-center justify-between py-10">
15+
<div>
16+
<Link href="/" aria-label={siteMetadata.headerTitle}>
17+
<div className="flex items-center justify-between">
18+
<div className="mr-3">
19+
<Logo />
20+
</div>
21+
{typeof siteMetadata.headerTitle === 'string' ? (
22+
<div className="hidden h-6 text-2xl font-semibold sm:block">
23+
{siteMetadata.headerTitle}
24+
</div>
25+
) : (
26+
siteMetadata.headerTitle
27+
)}
28+
</div>
29+
</Link>
30+
</div>
31+
<div className="flex items-center text-base leading-5">
32+
<div className="hidden sm:block">
33+
{headerNavLinks.map((link) => (
34+
<Link
35+
key={link.title}
36+
href={link.href}
37+
className="p-1 font-medium text-gray-900 dark:text-gray-100 sm:p-4"
38+
>
39+
{link.title}
40+
</Link>
41+
))}
42+
</div>
43+
<ThemeSwitch />
44+
<MobileNav />
45+
</div>
46+
</header>
47+
<main className="mb-auto">{children}</main>
48+
<Footer />
49+
</div>
50+
</SectionContainer>
51+
)
52+
}
53+
54+
export default LayoutWrapper

components/Link.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/* eslint-disable jsx-a11y/anchor-has-content */
2+
import Link from 'next/link'
3+
4+
const CustomLink = ({ href, ...rest }) => {
5+
const isInternalLink = href && href.startsWith('/')
6+
const isAnchorLink = href && href.startsWith('#')
7+
8+
if (isInternalLink) {
9+
return (
10+
<Link href={href}>
11+
<a {...rest} />
12+
</Link>
13+
)
14+
}
15+
16+
if (isAnchorLink) {
17+
return <a href={href} {...rest} />
18+
}
19+
20+
return <a target="_blank" rel="noopener noreferrer" href={href} {...rest} />
21+
}
22+
23+
export default CustomLink

components/MDXComponents.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/* eslint-disable react/display-name */
2+
import { useMemo } from 'react'
3+
import { getMDXComponent } from 'mdx-bundler/client'
4+
import Image from './Image'
5+
import CustomLink from './Link'
6+
import TOCInline from './TOCInline'
7+
import Pre from './Pre'
8+
import { BlogNewsletterForm } from './NewsletterForm'
9+
10+
export const MDXComponents = {
11+
Image,
12+
TOCInline,
13+
a: CustomLink,
14+
pre: Pre,
15+
BlogNewsletterForm: BlogNewsletterForm,
16+
wrapper: ({ components, layout, ...rest }) => {
17+
const Layout = require(`../layouts/${layout}`).default
18+
return <Layout {...rest} />
19+
},
20+
}
21+
22+
export const MDXLayoutRenderer = ({ layout, mdxSource, ...rest }) => {
23+
const MDXLayout = useMemo(() => getMDXComponent(mdxSource), [mdxSource])
24+
25+
return <MDXLayout layout={layout} components={MDXComponents} {...rest} />
26+
}

components/MobileNav.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { useState } from 'react'
2+
import Link from './Link'
3+
import headerNavLinks from '@/data/headerNavLinks'
4+
5+
const MobileNav = () => {
6+
const [navShow, setNavShow] = useState(false)
7+
8+
const onToggleNav = () => {
9+
setNavShow((status) => {
10+
if (status) {
11+
document.body.style.overflow = 'auto'
12+
} else {
13+
// Prevent scrolling
14+
document.body.style.overflow = 'hidden'
15+
}
16+
return !status
17+
})
18+
}
19+
20+
return (
21+
<div className="sm:hidden">
22+
<button
23+
type="button"
24+
className="ml-1 mr-1 h-8 w-8 rounded py-1"
25+
aria-label="Toggle Menu"
26+
onClick={onToggleNav}
27+
>
28+
<svg
29+
xmlns="http://www.w3.org/2000/svg"
30+
viewBox="0 0 20 20"
31+
fill="currentColor"
32+
className="text-gray-900 dark:text-gray-100"
33+
>
34+
<path
35+
fillRule="evenodd"
36+
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
37+
clipRule="evenodd"
38+
/>
39+
</svg>
40+
</button>
41+
<div
42+
className={`fixed top-0 left-0 z-10 h-full w-full transform bg-gray-200 opacity-95 duration-300 ease-in-out dark:bg-gray-800 ${
43+
navShow ? 'translate-x-0' : 'translate-x-full'
44+
}`}
45+
>
46+
<div className="flex justify-end">
47+
<button
48+
type="button"
49+
className="mr-5 mt-11 h-8 w-8 rounded"
50+
aria-label="Toggle Menu"
51+
onClick={onToggleNav}
52+
>
53+
<svg
54+
xmlns="http://www.w3.org/2000/svg"
55+
viewBox="0 0 20 20"
56+
fill="currentColor"
57+
className="text-gray-900 dark:text-gray-100"
58+
>
59+
<path
60+
fillRule="evenodd"
61+
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
62+
clipRule="evenodd"
63+
/>
64+
</svg>
65+
</button>
66+
</div>
67+
<nav className="fixed mt-8 h-full">
68+
{headerNavLinks.map((link) => (
69+
<div key={link.title} className="px-12 py-4">
70+
<Link
71+
href={link.href}
72+
className="text-2xl font-bold tracking-widest text-gray-900 dark:text-gray-100"
73+
onClick={onToggleNav}
74+
>
75+
{link.title}
76+
</Link>
77+
</div>
78+
))}
79+
</nav>
80+
</div>
81+
</div>
82+
)
83+
}
84+
85+
export default MobileNav

components/NewsletterForm.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { useRef, useState } from 'react'
2+
3+
import siteMetadata from '@/data/siteMetadata'
4+
5+
const NewsletterForm = ({ title = 'Subscribe to the newsletter' }) => {
6+
const inputEl = useRef(null)
7+
const [error, setError] = useState(false)
8+
const [message, setMessage] = useState('')
9+
const [subscribed, setSubscribed] = useState(false)
10+
11+
const subscribe = async (e) => {
12+
e.preventDefault()
13+
14+
const res = await fetch(`/api/${siteMetadata.newsletter.provider}`, {
15+
body: JSON.stringify({
16+
email: inputEl.current.value,
17+
}),
18+
headers: {
19+
'Content-Type': 'application/json',
20+
},
21+
method: 'POST',
22+
})
23+
24+
const { error } = await res.json()
25+
if (error) {
26+
setError(true)
27+
setMessage('Your e-mail address is invalid or you are already subscribed!')
28+
return
29+
}
30+
31+
inputEl.current.value = ''
32+
setError(false)
33+
setSubscribed(true)
34+
setMessage('Successfully! 🎉 You are now subscribed.')
35+
}
36+
37+
return (
38+
<div>
39+
<div className="pb-1 text-lg font-semibold text-gray-800 dark:text-gray-100">{title}</div>
40+
<form className="flex flex-col sm:flex-row" onSubmit={subscribe}>
41+
<div>
42+
<label className="sr-only" htmlFor="email-input">
43+
Email address
44+
</label>
45+
<input
46+
autoComplete="email"
47+
className="w-72 rounded-md px-4 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-primary-600 dark:bg-black"
48+
id="email-input"
49+
name="email"
50+
placeholder={subscribed ? "You're subscribed ! 🎉" : 'Enter your email'}
51+
ref={inputEl}
52+
required
53+
type="email"
54+
disabled={subscribed}
55+
/>
56+
</div>
57+
<div className="mt-2 flex w-full rounded-md shadow-sm sm:mt-0 sm:ml-3">
58+
<button
59+
className={`w-full rounded-md bg-primary-500 py-2 px-4 font-medium text-white sm:py-0 ${
60+
subscribed ? 'cursor-default' : 'hover:bg-primary-700 dark:hover:bg-primary-400'
61+
} focus:outline-none focus:ring-2 focus:ring-primary-600 focus:ring-offset-2 dark:ring-offset-black`}
62+
type="submit"
63+
disabled={subscribed}
64+
>
65+
{subscribed ? 'Thank you!' : 'Sign up'}
66+
</button>
67+
</div>
68+
</form>
69+
{error && (
70+
<div className="w-72 pt-2 text-sm text-red-500 dark:text-red-400 sm:w-96">{message}</div>
71+
)}
72+
</div>
73+
)
74+
}
75+
76+
export default NewsletterForm
77+
78+
export const BlogNewsletterForm = ({ title }) => (
79+
<div className="flex items-center justify-center">
80+
<div className="bg-gray-100 p-6 dark:bg-gray-800 sm:px-14 sm:py-8">
81+
<NewsletterForm title={title} />
82+
</div>
83+
</div>
84+
)

components/PageTitle.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function PageTitle({ children }) {
2+
return (
3+
<h1 className="text-3xl font-extrabold leading-9 tracking-tight text-gray-900 dark:text-gray-100 sm:text-4xl sm:leading-10 md:text-5xl md:leading-14">
4+
{children}
5+
</h1>
6+
)
7+
}

0 commit comments

Comments
 (0)