Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create subscribe form #126

Merged
merged 4 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@

1. (Recommended) Use VSCode and install the recommended extensions
2. Run `npm run install` to install all of the dependencies
3. Create a `.env.production.local` file and put the following contents in it
3. Create a `.env.local` file and put the following keys in it

```shell
BEEHIIV_PUBLICATION_ID="<beehiiv publication id>"
BEEHIIV_API_KEY="<beehiiv api key>"
```

4. Create a `.env.production.local` file and put the following contents in it

```.env
NEXT_PUBLIC_VERCEL_URL=ryanclements.dev
Expand Down
76 changes: 76 additions & 0 deletions app/api/subscribe/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* eslint-disable no-console */

export async function POST(request: Request): Promise<Response> {
const publicationId = process.env.BEEHIIV_PUBLICATION_ID
if (!publicationId) {
console.error('Must have BEEHIIV_PUBLICATION_ID defined')
return Response.json(
{
message: 'Oops! Something went wrong'
},
{
status: 500
}
)
}
const apiKey = process.env.BEEHIIV_API_KEY
if (!apiKey) {
console.error('Must have BEEHIIV_API_KEY defined')
return Response.json(
{
message: 'Oops! Something went wrong'
},
{
status: 500
}
)
}

const { email, referringUrl } = await request.json()
if (!email) {
return Response.json(
{
message: 'Please submit a valid email'
},
{
status: 400
}
)
}
const url = new URL(referringUrl)
// https://developers.beehiiv.com/docs/v2/1a77a563675ee-create
const response = await fetch(
`https://api.beehiiv.com/v2/publications/${publicationId}/subscriptions`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
Accept: 'application/json'
},
body: JSON.stringify({
email,
reactivate_existing: true,
send_welcome_email: true,
utm_source: url.searchParams.get('ref') || url.host,
referring_site: referringUrl,
utm_medium: url.host
})
}
)
if (!response.ok) {
console.error(await response.json())
return Response.json(
{
message: 'Oops! Something went wrong.'
},
{
status: 500
}
)
} else {
return Response.json({
message: "You've been subscribed!"
})
}
}
73 changes: 73 additions & 0 deletions components/pages/posts/[slug]/SubscribeForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
'use client'

import { useState } from 'react'

interface FormState {
ok: boolean
message: string
}

const initialState: FormState = {
ok: true,
message: ''
}

export function SubscribeForm(): JSX.Element {
const [formState, setFormState] = useState(initialState)
return (
<form
className="mx-auto flex flex-col gap-8 text-on-surface-base"
onSubmit={async e => {
e.preventDefault()
const email = e.currentTarget.email.value
const res = await fetch('/api/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, referringUrl: window.location.href })
})
const { message } = await res.json()
if (res.ok) {
;(e.target as HTMLFormElement).reset()
}
setFormState({
ok: res.ok,
message
})
}}
>
<h3 className="text-xl">
Subscribe to receive more like this in your inbox.
</h3>
<p className="text-on-surface-base-muted">
No spam <span className="italic">ever.</span>
</p>
<div className="mx-auto flex flex-col justify-center gap-4 md:flex-row">
<label htmlFor="email" className="sr-only">
Email
</label>
<input
required
type="text"
name="email"
className="overflow-hidden rounded-md border border-borderColor bg-surface-base px-3 py-2 text-left text-sm shadow-sm focus:border-borderColor-focus focus:outline-none focus:ring-1 focus:ring-ringColor-focus"
placeholder="Enter your email"
/>
<button className="rounded-md bg-accent-400 px-3 py-2 text-sm text-on-surface-accent shadow-sm hover:bg-surface-accent-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-surface-accent">
Subscribe
</button>
</div>
{formState.message && (
<p
aria-live="polite"
className={`text-sm ${
formState.ok === true ? 'text-success' : 'text-danger'
}`}
>
{formState.message}
</p>
)}
</form>
)
}
8 changes: 4 additions & 4 deletions components/pages/posts/[slug]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Layout } from 'components/Layout'
import { A11yStaticImageData } from 'lib/content/images'
import Link from 'next/link'
import { ShareLinks } from 'components/ShareLinks'
import { SubscribeForm } from './SubscribeForm'

export interface PostDetailsProps {
post: RenderablePost
Expand Down Expand Up @@ -52,12 +53,11 @@ const ContentContainer: React.FC<{ children?: React.ReactNode }> = ({
const Closer: React.FC<{ title: string }> = ({ title }) => (
<section className="flex flex-col gap-12 text-center">
<hr className="border-borderColor" />
<p>
Did you enjoy the post? Consider supporting me and my tea addition 🤗🍵.
</p>
<SubscribeForm />
<p>You can also support me and my tea addition 🤗🍵.</p>
<BuyMeACoffeeButton />
<p className="mx-auto flex gap-2">
Or sharing with others
Or share with others
<ShareLinks title={title} />
</p>
</section>
Expand Down
10 changes: 10 additions & 0 deletions tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export default {
offBase: colors.gray[200],
brand: primary.DEFAULT,
primary: primary[100],
accent: {
DEFAULT: colors.yellow[300],
hover: colors.yellow[500]
},
active: primary[100],
success: colors.green[100],
warning: colors.yellow[50],
Expand All @@ -78,6 +82,7 @@ export default {
},
brand: primary[100],
primary: primary[900],
accent: colors.yellow[900],
active: colors.zinc[900],
success: colors.green[900],
warning: colors.yellow[900],
Expand Down Expand Up @@ -138,6 +143,10 @@ export default {
offBase: colors.gray[800],
brand: primary[800],
primary: '#1f2330', // primary[900] @ 50% opacity on colors.zinc[900]
accent: {
DEFAULT: colors.amber[300],
hover: colors.amber[500]
},
active: primary[900],
success: '#172a21', // colors.green[800] @ 30% opacity on colors.zinc[900]
warning: '#392817', // colors.yellow[800] @ 50% opacity on colors.zinc[900]
Expand All @@ -155,6 +164,7 @@ export default {
},
brand: primary[100],
primary: primary[100],
accent: colors.amber[900],
active: primary[100],
success: colors.green[200],
warning: colors.yellow[200],
Expand Down
Loading