Skip to content

Commit 45d35e3

Browse files
committed
feat: add newsletter for regular updates
1 parent 4fcfe78 commit 45d35e3

File tree

4 files changed

+159
-23
lines changed

4 files changed

+159
-23
lines changed

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
MAILCHIMP_API_KEY="1223123123123-12312312312dasasdaswaqde1212132bgvegthr45uy623"
2+
MAILCHIMP_LIST_ID="12312131231ascxaqsdedas"

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,6 @@ yarn-error.log*
3434
# typescript
3535
*.tsbuildinfo
3636
next-env.d.ts
37+
38+
39+
.env

src/components/Newsletter.tsx

Lines changed: 106 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import { useTranslation } from 'next-i18next'
2-
import { Button, Flex, Input } from 'dd360-ds'
2+
import { useCallback, useEffect, useMemo, useState } from 'react'
3+
import {
4+
Button,
5+
Flex,
6+
Input,
7+
Callout,
8+
CalloutProps,
9+
Transition
10+
} from 'dd360-ds'
311
import { composeClasses } from 'dd360-ds/lib'
412
import { useTheme } from '@/store/theme-store'
513

@@ -8,30 +16,105 @@ function Newsletter() {
816
const {
917
themeObject: { extendedPalette }
1018
} = useTheme()
19+
const [emailVal, setEmailVal] = useState('')
20+
const [isLoading, setIsLoading] = useState(false)
21+
const [isMessageShown, setIsMessageShown] = useState(false)
22+
const [messageType, setMessageType] = useState('')
23+
24+
const isDisabledBtn = emailVal === '' || isMessageShown
25+
26+
const sendSubmit = useCallback(async () => {
27+
setIsLoading(true)
28+
try {
29+
const res = await fetch('/api/newsletter', {
30+
method: 'POST',
31+
body: emailVal
32+
})
33+
34+
const data = await res.json()
35+
if (data.status !== 'success') {
36+
throw new Error('Failed')
37+
}
38+
setMessageType('success')
39+
} catch (e) {
40+
setMessageType('error')
41+
} finally {
42+
setIsMessageShown(true)
43+
setIsLoading(false)
44+
}
45+
}, [emailVal])
46+
47+
const calloutProps = useMemo(() => {
48+
return {
49+
title:
50+
messageType === 'success'
51+
? 'Thanks for subscribing!'
52+
: 'Something went wrong!',
53+
description:
54+
messageType === 'success'
55+
? "We'll send you an email when we have news."
56+
: "Oops, there was an error and we couldn't subscribe you. Please try again.",
57+
variant:
58+
messageType === 'success'
59+
? 'success'
60+
: ('error' as CalloutProps['variant'])
61+
}
62+
}, [messageType])
63+
64+
useEffect(() => {
65+
if (isMessageShown) {
66+
const timer = setTimeout(() => {
67+
setIsMessageShown(false)
68+
setMessageType('')
69+
}, 5000)
70+
71+
return () => clearTimeout(timer)
72+
}
73+
}, [isMessageShown])
1174

1275
return (
13-
<Flex gap="3" className="flex-col sm:flex-row mt-8">
14-
<Input
15-
placeholder="hello@dd360.mx"
16-
className={composeClasses(
17-
'sm:mt-0 inline-block border-gray-500 w-full sm:max-w-[287px]',
18-
extendedPalette.primaryText
19-
)}
20-
style={{
21-
background: extendedPalette.inputBackground,
22-
borderColor: extendedPalette.inputBorderHex
23-
}}
24-
/>
25-
<Button
26-
className="leading-none text-gray-50 min-w-max"
27-
rounded="lg"
28-
paddingX="8"
29-
paddingY="4"
30-
style={{ maxWidth: 124 }}
31-
>
32-
{t('subscribe')}
33-
</Button>
34-
</Flex>
76+
<>
77+
<Flex gap="3" className="flex-col sm:flex-row mt-8">
78+
<Input
79+
placeholder="hello@dd360.mx"
80+
type="email"
81+
className={composeClasses(
82+
'sm:mt-0 inline-block border-gray-500 w-full sm:max-w-[287px]',
83+
extendedPalette.primaryText
84+
)}
85+
onChange={(e) => setEmailVal(e.target.value)}
86+
onSubmit={(e) => {
87+
e.preventDefault()
88+
sendSubmit()
89+
}}
90+
value={emailVal}
91+
style={{
92+
background: extendedPalette.inputBackground,
93+
borderColor: extendedPalette.inputBorderHex
94+
}}
95+
/>
96+
<Button
97+
className="leading-none text-gray-50 min-w-max"
98+
rounded="lg"
99+
paddingX="8"
100+
paddingY="4"
101+
style={{ maxWidth: 124 }}
102+
disabled={isDisabledBtn || isLoading}
103+
isLoading={isLoading}
104+
onClick={(e) => {
105+
e.preventDefault()
106+
sendSubmit()
107+
}}
108+
>
109+
{t('subscribe')}
110+
</Button>
111+
</Flex>
112+
{isMessageShown && (
113+
<Transition show>
114+
<Callout className="w-96 mt-4" {...calloutProps} />
115+
</Transition>
116+
)}
117+
</>
35118
)
36119
}
37120

src/pages/api/newsletter.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { NextApiRequest, NextApiResponse } from 'next'
2+
3+
const API_KEY = process.env.MAILCHIMP_API_KEY
4+
const LIST_ID = process.env.MAILCHIMP_LIST_ID
5+
const DATACENTER = API_KEY?.split('-')[1]
6+
7+
function getRequestParams(email: string) {
8+
const url = `https://${DATACENTER}.api.mailchimp.com/3.0/lists/${LIST_ID}/members`
9+
10+
const data = {
11+
email_address: email,
12+
status: 'subscribed'
13+
}
14+
15+
const base64ApiKey = Buffer.from(`anystring:${API_KEY}`).toString('base64')
16+
const headers = {
17+
'Content-Type': 'application/json',
18+
Authorization: `Basic ${base64ApiKey}`
19+
}
20+
21+
return { url, data, headers }
22+
}
23+
24+
export default async function handler(
25+
req: NextApiRequest,
26+
res: NextApiResponse<{ status: 'error' | 'success' }>
27+
) {
28+
const email = req.body
29+
30+
try {
31+
const { url, data, headers } = getRequestParams(email)
32+
33+
const response = await fetch(url, {
34+
method: 'POST',
35+
headers: headers,
36+
body: JSON.stringify({
37+
email_address: data.email_address,
38+
status: data.status
39+
})
40+
})
41+
42+
if (!response.ok) res.status(400).json({ status: 'error' })
43+
44+
res.status(200).json({ status: 'success' })
45+
} catch (err) {
46+
res.status(400).json({ status: 'error' })
47+
}
48+
}

0 commit comments

Comments
 (0)