From 6e5b50d55a1951707d5f5f4cc76dc90ac2a54d1f Mon Sep 17 00:00:00 2001 From: Simon Vrachliotis Date: Wed, 21 Aug 2024 09:14:49 +1000 Subject: [PATCH] =?UTF-8?q?Add=20buttondown=20for=20the=20`/docs`=20newsle?= =?UTF-8?q?tter=20subscription=20using=20server=20actions=20=F0=9F=98=8E?= =?UTF-8?q?=20(#9293)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + docs/.env.example | 1 + docs/app/actions.ts | 43 +++++++++ docs/components/SubscribeForm.tsx | 141 +++++++++++++++++------------- 4 files changed, 125 insertions(+), 61 deletions(-) create mode 100644 docs/.env.example create mode 100644 docs/app/actions.ts diff --git a/.gitignore b/.gitignore index 9a1df743cce..5e99da02ce9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ dist/ node_modules/ *.db +.env # ts-gql __generated__ diff --git a/docs/.env.example b/docs/.env.example new file mode 100644 index 00000000000..493eb515408 --- /dev/null +++ b/docs/.env.example @@ -0,0 +1 @@ +BUTTONDOWN_API_KEY= \ No newline at end of file diff --git a/docs/app/actions.ts b/docs/app/actions.ts new file mode 100644 index 00000000000..5ce5a3a653e --- /dev/null +++ b/docs/app/actions.ts @@ -0,0 +1,43 @@ +'use server' + +// ------------------------------ +// Buttondown subscription +// ------------------------------ +export async function subscribeToButtondown (pathname: string, formData: FormData) { + try { + const data = { + email: formData.get('email'), + tags: [ + ...formData.getAll('tags'), + `keystone website${pathname !== '/' ? `: ${pathname}` : ' homepage'}`, + ], + } + + const buttondownResponse = await fetch('https://api.buttondown.email/v1/subscribers', { + method: 'POST', + headers: { + Authorization: `Token ${process.env.BUTTONDOWN_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email_address: data.email, + tags: data.tags, + }), + }) + + if (!buttondownResponse.ok) { + const error = await buttondownResponse.json() + return { + // 409 status Conflict has no detail message + error: error?.detail || 'Sorry, an error has occurred — please try again later.', + } + } + + return { success: true } + } catch (error) { + console.error('An error occurred: ', error) + return { + error: 'Sorry, an error has occurred — please try again later.', + } + } +} diff --git a/docs/components/SubscribeForm.tsx b/docs/components/SubscribeForm.tsx index 097a283a8b2..4683951f0be 100644 --- a/docs/components/SubscribeForm.tsx +++ b/docs/components/SubscribeForm.tsx @@ -1,18 +1,14 @@ /** @jsxImportSource @emotion/react */ -import { Fragment, useState, type ReactNode, type SyntheticEvent, type HTMLAttributes } from 'react' +import { Fragment, useState, type ReactNode, type HTMLAttributes, useTransition } from 'react' + +import { subscribeToButtondown } from '../app/actions' import { useMediaQuery } from '../lib/media' import { Button } from './primitives/Button' import { Field } from './primitives/Field' import { Stack } from './primitives/Stack' - -const validEmail = (email: string) => - /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test( - email - ) - -const signupURL = 'https://signup.keystonejs.cloud/api/newsletter-signup' +import { usePathname } from 'next/navigation' type SubscriptFormProps = { autoFocus?: boolean @@ -21,89 +17,112 @@ type SubscriptFormProps = { } & HTMLAttributes export function SubscribeForm ({ autoFocus, stacked, children, ...props }: SubscriptFormProps) { - const [email, setEmail] = useState('') - const [loading, setLoading] = useState(false) + const pathname = usePathname() + const mq = useMediaQuery() + const [isPending, startTransition] = useTransition() + const [error, setError] = useState(null) const [formSubmitted, setFormSubmitted] = useState(false) - const mq = useMediaQuery() - const onSubmit = (event: SyntheticEvent) => { - event.preventDefault() - setError(null) - // Check if user wants to subscribe. - // and there's a valid email address. - // Basic validation check on the email? - setLoading(true) - if (validEmail(email)) { - // if good add email to mailing list - // and redirect to dashboard. - return fetch(signupURL, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - email, - source: '@keystone-6/website', - }), - }) - .then(res => { - if (res.status !== 200) { - // We explicitly set the status in our endpoint - // any status that isn't 200 we assume is a failure - // which we want to surface to the user - res.json().then(({ error }) => { - setError(error) - setLoading(false) - }) - } else { - setFormSubmitted(true) - } - }) - .catch(err => { - // network errors or failed parse - setError(err.toString()) - setLoading(false) - }) - } else { - setLoading(false) - // if email fails validation set error message - setError('Please enter a valid email') - return - } + // Augment the server action with the pathname + const subscribeToButtondownWithPathname = subscribeToButtondown.bind(null, pathname) + + async function submitAction (formData: FormData) { + startTransition(async () => { + const response = await subscribeToButtondownWithPathname(formData) + if (response.error) return setError(response.error) + if (response.success) return setFormSubmitted(true) + }) } return !formSubmitted ? ( {children} -
+ + setEmail(e.target.value)} css={mq({ maxWidth: '25rem', margin: ['0 auto', 0], })} + name="email" + id="email" + required /> - - {error ?

{error}

: null} + {error ? ( +

{error}

+ ) : null}
) : ( -

❤️ Thank you for subscribing!

+

❤️ Thank you! Please check your email to confirm your subscription.

) }