From 43e7efa96e37c116449c9bde0487e49179b9b36d Mon Sep 17 00:00:00 2001 From: joshua berman Date: Wed, 8 May 2024 10:26:16 +1000 Subject: [PATCH 01/12] Modal-pop ups --- components/forms/EmailForm.tsx | 107 +++++++++++++++++++-- components/ui/ModalConfirmation.module.css | 13 +++ components/ui/ModalConfirmation.tsx | 27 ++++++ package.json | 1 + tina/tina-lock.json | 2 +- yarn.lock | 31 ++++++ 6 files changed, 171 insertions(+), 10 deletions(-) create mode 100644 components/ui/ModalConfirmation.module.css create mode 100644 components/ui/ModalConfirmation.tsx diff --git a/components/forms/EmailForm.tsx b/components/forms/EmailForm.tsx index 4656e8144..136ab4414 100644 --- a/components/forms/EmailForm.tsx +++ b/components/forms/EmailForm.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react' import styled, { css } from 'styled-components' import { addToMailchimp } from '../../utils' import { Input, Button } from '../ui' +import ModalConfirmation from 'components/ui/ModalConfirmation' interface EmailFormProps { isFooter: boolean @@ -10,20 +11,36 @@ interface EmailFormProps { export const EmailForm = (props: EmailFormProps) => { const [email, setEmail] = useState('') const [isEntering, setIsEntering] = useState(false) + const [isSuccessOpen, setIsSuccessOpen] = useState(false); + const [isErrorOpen, setIsErrorOpen] = useState(false); + + const handleSuccessModal = () => { + setIsSuccessOpen(true); +}; + +const handleCloseModal = () => { + setIsSuccessOpen(false); +}; + +const handleOpenErrorModal = () => { + setIsErrorOpen(true); +}; + +const handleCloseErrorModal = () => { + setIsErrorOpen(false); +}; const handleSubmit = (e: React.FormEvent) => { e.preventDefault() addToMailchimp(email) .then((data: any) => { - alert(data.msg) + handleOpenErrorModal(); }) .catch((error: Error) => { // Errors in here are client side // Mailchimp always returns a 200 if (error.message === 'Timeout') { - alert( - 'Looks like your browser is blocking this. Try to disable any tracker-blocking feature and resubmit.' - ) + handleSuccessModal(); } console.error(error) }) @@ -48,6 +65,83 @@ export const EmailForm = (props: EmailFormProps) => { onChange={handleEmailChange} onFocus={handleEmailChange} /> + {isSuccessOpen && ( + +

+ Welcome Aboard! +

+

+ You've been added to the llama list. +

+
+ +
+ + } + /> +)} + +{isErrorOpen && ( + +

+ Hold your llamas! +

+

+ Couldn't sign you up. Please retry or contact us! +

+
+ +
+ + } + /> +)} {props.isFooter ? ( isEntering && ( @@ -109,7 +103,7 @@ const handleSubmit = async (e: React.FormEvent) => { {isErrorOpen && ( ) => { justifyContent: 'flex-end', }} > - + + + } + /> +)} + +{isDuplicateOpen && ( + +

+ Already Subscribed +

+

+ You're already in our herd! Missing our emails? Let's fix that! +

+
+ +
} diff --git a/pages/api/mailchimp.ts b/pages/api/mailchimp.ts index e41c5efd0..bbb642e61 100644 --- a/pages/api/mailchimp.ts +++ b/pages/api/mailchimp.ts @@ -1,10 +1,23 @@ -// mailchimp.ts +import mailchimp from '@mailchimp/mailchimp_marketing' -import mailchimp from '@mailchimp/mailchimp_marketing'; +export default async function handler(req, res) { + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed' }) + } -mailchimp.setConfig({ - apiKey: process.env.MAILCHIMP_API_KEY || '', - server: process.env.MAILCHIMP_SERVER_PREFIX || '', -}); + mailchimp.setConfig({ + apiKey: process.env.MAILCHIMP_API_KEY, + server: process.env.MAILCHIMP_SERVER_PREFIX, + }) -export default mailchimp; + const { email_address, status, merge_fields } = req.body + try { + const response = await mailchimp.lists.addListMember( + process.env.MAILCHIMP_AUDIENCE_ID, + { email_address, status, merge_fields } + ) + res.status(200).json({ success: true, response }) + } catch (err) { + res.status(500).json({ error: true, message: err.message }) + } +} diff --git a/tsconfig.json b/tsconfig.json index b227120c6..4f7c0036d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,5 +27,5 @@ "next-env.d.ts", "**/*.ts", "**/*.tsx" - ] +, "utils/useToggle.tsx" ] } diff --git a/utils/mailchimp_helper.ts b/utils/mailchimp_helper.ts index 32be6b6a5..2cd5e4265 100644 --- a/utils/mailchimp_helper.ts +++ b/utils/mailchimp_helper.ts @@ -1,35 +1,43 @@ -import mailchimp from '../pages/api/mailchimp'; -import { validate } from 'email-validator'; +import { validate } from 'email-validator' interface SubscriptionResult { - result: 'success' | 'error'; - message: string; + result: 'success' | 'error' + message: string } -export async function addToMailchimp(email: string): Promise { +export async function addToMailchimp( + email: string +): Promise { if (!validate(email)) { return { result: 'error', - message: 'The email you entered is not valid.' - }; + message: 'The email you entered is not valid.', + } } try { - const listId = process.env.MAILCHIMP_LIST_ID || ''; - const response = await mailchimp.lists.addListMember(listId, { - email_address: email, - status: 'pending' - }); - console.log('API response:', response); + const response = await fetch('/api/mailchimp', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + email_address: email, + status: 'subscribed', + merge_fields: {}, + }), + }) + + const data = await response.json() + if (!response.ok) { + throw new Error(data.message || 'Failed to add email to the list.') + } return { result: 'success', - message: 'Email successfully added to the list.' - }; + message: 'Email successfully added to the list.', + } } catch (error) { - console.error('Error adding to Mailchimp:', error); return { result: 'error', - message: 'Failed to add email to the list.' - }; + message: error.message || 'Failed to add email to the list.', + } } } diff --git a/utils/useToggle.tsx b/utils/useToggle.tsx new file mode 100644 index 000000000..ae48510e5 --- /dev/null +++ b/utils/useToggle.tsx @@ -0,0 +1,10 @@ +import { Reducer, useReducer } from 'react'; + +const toggleReducer = (state: boolean, nextValue?: any) => + typeof nextValue === 'boolean' ? nextValue : !state; + +const useToggle = (initialValue: boolean): [boolean, (nextValue?: any) => void] => { + return useReducer>(toggleReducer, initialValue); +}; + +export default useToggle; \ No newline at end of file From 23c1d884ef188ef43d00a7fa9c9ab751a5e355e1 Mon Sep 17 00:00:00 2001 From: joshua berman Date: Mon, 13 May 2024 12:15:41 +1000 Subject: [PATCH 09/12] Remove css and inline style. Replace w/ Tailwind --- components/forms/EmailForm.tsx | 179 ++++++++------------- components/ui/ModalConfirmation.module.css | 13 -- components/ui/ModalConfirmation.tsx | 5 +- tsconfig.json | 3 +- 4 files changed, 68 insertions(+), 132 deletions(-) delete mode 100644 components/ui/ModalConfirmation.module.css diff --git a/components/forms/EmailForm.tsx b/components/forms/EmailForm.tsx index 64ed92e01..199cd9808 100644 --- a/components/forms/EmailForm.tsx +++ b/components/forms/EmailForm.tsx @@ -60,122 +60,73 @@ const handleSubmit = async (e: React.FormEvent) => { onFocus={handleEmailChange} /> {isSuccessOpen && ( - -

- Welcome Aboard! -

-

- You've been added to the llama list. -

-
- -
- - } - /> -)} + +

+ Welcome Aboard! +

+

+ You've been added to the llama list. +

+
+ +
+ + } + /> + )} -{isErrorOpen && ( - -

- Hold your llamas! -

-

- Couldn't sign you up. Please retry or contact us! -

-
- -
- - } - /> -)} + {isErrorOpen && ( + +

+ Hold your llamas! +

+

+ Couldn't sign you up. Please retry or contact us! +

+
+ +
+ + } + /> + )} -{isDuplicateOpen && ( - -

- Already Subscribed -

-

- You're already in our herd! Missing our emails? Let's fix that! -

-
- - -
- - } - /> -)} + {isDuplicateOpen && ( + +

+ Already Subscribed +

+

+ You're already in our herd! Missing our emails? Let's fix that! +

+
+ + +
+ + } + /> + )} {props.isFooter ? ( isEntering && ( - From ca8712b9ae437194ff5de39c54ba4556068826ef Mon Sep 17 00:00:00 2001 From: joshua berman Date: Mon, 13 May 2024 16:37:57 +1000 Subject: [PATCH 11/12] change sizing modal --- components/ui/ModalConfirmation.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/components/ui/ModalConfirmation.tsx b/components/ui/ModalConfirmation.tsx index de8c2b1b2..b432f2d3d 100644 --- a/components/ui/ModalConfirmation.tsx +++ b/components/ui/ModalConfirmation.tsx @@ -11,13 +11,14 @@ interface ModalConfirmationProps { const ModalConfirmation: React.FC = ({ isOpen, onClose, body }) => { return ( + open={isOpen} + onClose={onClose} + center + classNames={{ + overlay: 'bg-gray-400 bg-opacity-80', + modal: 'bg-white w-11/12 sm:w-3/4 md:w-2/3 lg:w-1/3 max-w-5xl rounded-2xl p-4 text-left', + }} + >
{body}
); From e5645fd3f4fc88529f5af41abb746fc83df2f39e Mon Sep 17 00:00:00 2001 From: joshua berman Date: Mon, 13 May 2024 17:23:27 +1000 Subject: [PATCH 12/12] added loading gif and disabled spam click --- components/forms/EmailForm.tsx | 33 +++++++++++++++++++++------------ components/ui/Button.tsx | 1 + 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/components/forms/EmailForm.tsx b/components/forms/EmailForm.tsx index 2ee63b551..9f0540612 100644 --- a/components/forms/EmailForm.tsx +++ b/components/forms/EmailForm.tsx @@ -14,15 +14,18 @@ interface EmailFormProps { export const EmailForm = (props: EmailFormProps) => { const [email, setEmail] = useState('') const [isEntering, setIsEntering] = useState(false) + const [isProcessing, setIsProcessing] = useState(false); const [isSuccessOpen, toggleSuccess] = useToggle(false); const [isErrorOpen, toggleError] = useToggle(false); const [isDuplicateOpen, toggleDuplicate] = useToggle(false); + const {push} = useRouter() const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + setIsProcessing(true); try { const result = await addToMailchimp(email); if (result.result === 'success') { @@ -40,9 +43,9 @@ const handleSubmit = async (e: React.FormEvent) => { console.error('Error submitting email:', error); toggleError(); } + setIsProcessing(false); }; - const handleEmailChange = (event: React.ChangeEvent) => { setIsEntering(true) setEmail(event.currentTarget.value) @@ -61,7 +64,24 @@ const handleSubmit = async (e: React.FormEvent) => { type="text" onChange={handleEmailChange} onFocus={handleEmailChange} + disabled= {isProcessing} /> + {isEntering && ( + + )} {isSuccessOpen && ( ) => { } /> )} - {props.isFooter ? ( - isEntering && ( - - ) - ) : ( - - )} ) } diff --git a/components/ui/Button.tsx b/components/ui/Button.tsx index fc6299319..efbff0432 100644 --- a/components/ui/Button.tsx +++ b/components/ui/Button.tsx @@ -7,6 +7,7 @@ interface ButtonProps extends React.HTMLAttributes { href?: string type?: 'button' | 'submit' | 'reset' children: React.ReactNode | React.ReactNode[] + disabled?: boolean } const baseClasses =