Skip to content

Commit

Permalink
simplified subscription flow
Browse files Browse the repository at this point in the history
  • Loading branch information
andrecasal committed Mar 13, 2024
1 parent af9b29a commit 4e900d6
Show file tree
Hide file tree
Showing 15 changed files with 141 additions and 742 deletions.
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ DATABASE_URL="file:./data.db?connection_limit=1"
CACHE_DATABASE_PATH="./other/cache.db"
SESSION_SECRET="super-duper-s3cret"
INTERNAL_COMMAND_TOKEN="some-made-up-token"
TRANSACTIONAL_EMAIL_SERVICE_API_KEY="your-transactional-email-service-api-key"
TRANSACTIONAL_EMAIL_SERVICE_API_ENDPOINT="https://api.resend.com/emails"
RESEND_API_KEY="your-transactional-email-service-api-key"
RESEND_TRANSACTIONAL_ENDPOINT="https://api.resend.com/emails"
MARKETING_EMAIL_SERVICE_API_KEY="your-marketing-email-service-api-key"
MARKETING_EMAIL_SERVICE_API_ENDPOINT="https://connect.mailerlite.com/"
SENTRY_DSN="your-dsn"
Expand Down
49 changes: 15 additions & 34 deletions app/components/newsletter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Input } from '~/components/ui/input.tsx'
import { Label } from '~/components/ui/label.tsx'
import { useFetcher, useNavigation } from '@remix-run/react'
import { useForm } from '@conform-to/react'
import { type action, newsletterSchema } from '~/routes/_marketing+/newsletter/index.tsx'
import { type action, newsletterSchema } from '~/routes/_marketing+/newsletter.tsx'
import { parse } from '@conform-to/zod'
import { cn } from '~/utils/tailwind-merge.ts'
import guide from '~/routes/_marketing+/images/guide-to-modern-full-stack-web-dev.png'
Expand All @@ -13,9 +13,9 @@ import { VisuallyHidden } from '@radix-ui/react-visually-hidden'
import { Player } from '@lottiefiles/react-lottie-player'
import * as newsletterAnimation from '~/components/newsletter-animation.json'
import { useEffect, useRef, useState } from 'react'
import { B } from '~/routes/_marketing+/ui+/components/typography/b.tsx'
import { AuthenticityTokenInput } from 'remix-utils/csrf/react'
import { HoneypotInputs } from 'remix-utils/honeypot/react'
import { Flex } from '~/routes/_marketing+/ui+/components/layout/flex.tsx'

type NewsletterProps = {
className?: string
Expand All @@ -27,7 +27,7 @@ type NewsletterProps = {
const Newsletter = ({ className, title, description, buttonText }: NewsletterProps) => {
const fetcher = useFetcher<typeof action>()
const navigation = useNavigation()
const [form, { name, email }] = useForm({
const [form, { email }] = useForm({
lastSubmission: fetcher.data?.submission,
shouldValidate: 'onBlur',
onValidate: ({ formData }) => parse(formData, { schema: newsletterSchema }),
Expand Down Expand Up @@ -55,32 +55,17 @@ const Newsletter = ({ className, title, description, buttonText }: NewsletterPro
Once you subscribe you'll get my free guide to modern full-stack web development and solve analysis paralysis from choosing which tools to use.
</P>
</div>
<div className="grid">
<div className="mt-8 grid">
<fetcher.Form
method="post"
action="/newsletter"
className={`col-start-1 row-start-1 mt-space-13 flex flex-col transition-opacity ${state === 'initial' ? ' opacity-100' : 'pointer-events-none opacity-0'}`}
className={`col-start-1 row-start-1 ${state === 'initial' ? ' opacity-100' : 'pointer-events-none opacity-0'}`}
{...form.props}
>
<div className="mt-6 grid grid-cols-3 gap-x-space-13">
<div>
<VisuallyHidden>
<Label htmlFor="name">Name</Label>
</VisuallyHidden>
<Input
id="name"
type="text"
placeholder="Enter your name"
name="name"
required
defaultValue={name.defaultValue}
className={name.error ? 'border-danger-foreground' : ''}
/>
<P size="xs" className="ml-3.5 text-danger-foreground">
{name.error}&nbsp;
</P>
</div>
<div className="col-span-2">
<AuthenticityTokenInput />
<HoneypotInputs />
<Flex gap="6" className="w-full">
<Flex direction="col" className="w-full">
<VisuallyHidden>
<Label htmlFor="email-address">Email address</Label>
</VisuallyHidden>
Expand All @@ -97,13 +82,11 @@ const Newsletter = ({ className, title, description, buttonText }: NewsletterPro
<P size="xs" className="ml-3.5 text-danger-foreground">
{email.error}&nbsp;
</P>
</div>
</div>
<AuthenticityTokenInput />
<HoneypotInputs />
<Button type="submit" disabled={navigation.state === 'submitting'}>
{buttonText}
</Button>
</Flex>
<Button type="submit" disabled={navigation.state === 'submitting'} className="px-8">
{buttonText}
</Button>
</Flex>
</fetcher.Form>
<div className={`col-start-1 row-start-1 transition-opacity ${state === 'animating' ? 'opacity-100' : 'pointer-events-none opacity-0'}`}>
<Player
Expand All @@ -122,10 +105,8 @@ const Newsletter = ({ className, title, description, buttonText }: NewsletterPro
id="data-test-newsletter-finished-animation"
className={`col-start-1 row-start-1 mt-space-15 transition-opacity ${state === 'finished' ? 'opacity-100' : 'pointer-events-none opacity-0'}`}
>
<H2 align="center">Verify Your Email Address</H2>
<P align="center" className="mt-space-6">
<B className="text-success-foreground">Got it!</B> Please <B className="text-danger-foreground">check your email</B> to confirm your subscription, otherwise you
won't get my emails.
Got it, thanks!
</P>
<Button
size="wide"
Expand Down
64 changes: 64 additions & 0 deletions app/routes/_marketing+/newsletter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Container } from '~/routes/_marketing+/ui+/components/layout/container.tsx'
import { Resend } from 'resend'
import { json, type ActionArgs } from '@remix-run/node'
import { parse } from '@conform-to/zod'
import { z } from 'zod'
import { emailSchema } from '~/utils/user-validation.ts'
import { Newsletter as NewsletterComponent } from '~/components/newsletter.tsx'
import { validateCSRF } from '~/utils/csrf.server.ts'
import { checkHoneypot } from '~/utils/honeypot.server.ts'

export default function Newsletter() {
return (
<Container>
<NewsletterComponent
className="mt-32 sm:mt-44"
title="Get exclusive web dev tips that I only share with email subscribers"
description="Golden nuggets of code knowledge you can read in 5 minutes. Delivered to your inbox every 2 weeks."
buttonText="I want my tips"
/>
</Container>
)
}

export const newsletterEmailQueryParam = 'email'
export const newsletterOTPQueryParam = 'code'
export const newsletterVerificationType = 'newsletter'

export const newsletterSchema = z.object({
email: emailSchema,
})

export async function action({ request }: ActionArgs) {
const formData = await request.formData()

// Check for bots
await validateCSRF(formData, request.headers)
checkHoneypot(formData, '/newsletter')

// Parse form
const submission = parse(formData, { schema: newsletterSchema })
if (submission.intent !== 'submit') {
return json({ status: 'error', submission } as const)
}
if (!submission.value) {
return json({ status: 'error', submission } as const, { status: 400 })
}

// Extract data
const { email } = submission.value

// Subscribe to newsletter
const resend = new Resend(process.env.RESEND_API_KEY)
const result = await resend.contacts.create({
email,
unsubscribed: false,
audienceId: process.env.RESEND_GENERAL_AUDIENCE,
})
if (result.error) {
return json({ status: 'error', submission } as const, { status: 400 })
}

// Everything ok
return json({ status: 'success', submission } as const)
}
39 changes: 0 additions & 39 deletions app/routes/_marketing+/newsletter/email/verify.server.tsx

This file was deleted.

92 changes: 0 additions & 92 deletions app/routes/_marketing+/newsletter/index.tsx

This file was deleted.

Loading

0 comments on commit 4e900d6

Please sign in to comment.