diff --git a/package.json b/package.json index 4198c054..7ebdd544 100644 --- a/package.json +++ b/package.json @@ -1,35 +1,35 @@ { - "name": "celestia-org-nextjs-site", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "@tailwindcss/line-clamp": "^0.4.4", - "axios": "^1.7.2", - "framer-motion": "^11.2.13", - "gray-matter": "^4.0.3", - "markdown-to-jsx": "^7.4.7", - "motion": "^10.18.0", - "next": "14.2.4", - "react": "^18", - "react-dom": "^18", - "react-google-recaptcha": "^3.1.0", - "react-modal": "^3.16.1", - "react-player": "^2.16.0", - "react-slick": "^0.30.2", - "react-stickynode": "^4.1.1", - "sass": "^1.77.8", - "slick-carousel": "^1.8.1" - }, - "devDependencies": { - "eslint": "^8", - "eslint-config-next": "14.2.4", - "postcss": "^8", - "tailwindcss": "^3.4.1" - } + "name": "celestia-org-nextjs-site", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@tailwindcss/line-clamp": "^0.4.4", + "axios": "^1.7.2", + "framer-motion": "^11.2.13", + "gray-matter": "^4.0.3", + "markdown-to-jsx": "^7.4.7", + "motion": "^10.18.0", + "next": "14.2.4", + "react": "^18", + "react-dom": "^18", + "react-google-recaptcha": "^3.1.0", + "react-modal": "^3.16.1", + "react-player": "^2.16.0", + "react-slick": "^0.30.2", + "react-stickynode": "^4.1.1", + "sass": "^1.77.8", + "slick-carousel": "^1.8.1" + }, + "devDependencies": { + "eslint": "^8", + "eslint-config-next": "14.2.4", + "postcss": "^8", + "tailwindcss": "^3.4.1" + } } diff --git a/src/components/Newsletter/Newsletter.js b/src/components/Newsletter/Newsletter.js index 46b7035c..a64327c7 100644 --- a/src/components/Newsletter/Newsletter.js +++ b/src/components/Newsletter/Newsletter.js @@ -1,146 +1,193 @@ import React, { useState, useRef } from "react"; -import axios from "axios"; import ReCAPTCHA from "react-google-recaptcha"; -import Modal from "react-modal"; import PrimaryButton from "@/macros/Buttons/PrimaryButton"; -import { Row, Col } from "@/macros/Grids"; +import { Row } from "@/macros/Grids"; import { Body } from "@/macros/Copy"; -const Newsletter = (props) => { - const [email, setEmail] = useState(""); - const [listFields, setListFields] = useState({ "group[57543]": "1" }); - const [isModalOpen, setIsModalOpen] = useState(false); - const [status, setStatus] = useState(null); - const [msg, setMsg] = useState(""); - const [captchaError, setCaptchaError] = useState(""); +const Newsletter = () => { + const [email, setEmail] = useState(""); + const [status, setStatus] = useState(null); + const [msg, setMsg] = useState(""); + const [captchaError, setCaptchaError] = useState(""); + const [token, setToken] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + const reCaptchaRef = useRef(null); + const timeoutRef = useRef(null); - const siteKey = process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY; - const [token, setToken] = useState(null); - const reCaptchaRef = useRef(null); + const siteKey = process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY; - const handleChange = (e) => { - setEmail(e.target.value); - }; + const handleChange = (e) => { + setEmail(e.target.value); + // Reset status messages when user starts typing again + setStatus(null); + setMsg(""); + setCaptchaError(""); + }; const onReCAPTCHAChange = (token) => { setToken(token); setCaptchaError(""); }; - const asyncScriptOnLoad = () => { - console.log("reCAPTCHA script loaded"); - }; + const cleanup = (callbackName, scriptId) => { + // Clear timeout if it exists + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } - const mailchimp = async (url) => { - try { - const response = await axios.post(url, { - email, - ...listFields, - }); - const { data } = response; - setMsg(data.msg); - if ( - data.result === "error" && - data.msg.includes("is already subscribed") - ) { - setStatus("Success"); - setMsg("Thank you for subscribing!"); - } else { - setStatus(data.result === "success" ? "Success" : "Error"); - } - setIsModalOpen(true); - } catch (error) { - setStatus("Error"); - setMsg("An error occurred. Please try again later."); - setIsModalOpen(true); - } - }; + // Remove callback function + if (window[callbackName]) { + delete window[callbackName]; + } - const handleSubmit = async (e) => { - e.preventDefault(); - if (!token) { - setCaptchaError("Please complete the reCAPTCHA challenge!"); - return; - } - const updatedListFields = { ...listFields, "group[57543][1]": 1 }; - setListFields(updatedListFields); - if (email) { - mailchimp( - "https://celestia.us6.list-manage.com/subscribe/post?u=cde2461ba84f5279fff352829&id=8d165e36d3" - ); - } - }; + // Remove script tag + const scriptElement = document.getElementById(scriptId); + if (scriptElement) { + document.body.removeChild(scriptElement); + } + + setIsSubmitting(false); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + // Prevent multiple submissions + if (isSubmitting) return; + + // Verify reCAPTCHA + if (!token) { + setCaptchaError("Please complete the reCAPTCHA challenge!"); + return; + } + + setIsSubmitting(true); + + // Create unique IDs for this submission + const timestamp = Date.now(); + const callbackName = `jsonpCallback_${timestamp}`; + const scriptId = `mailchimpScript_${timestamp}`; + + // Set timeout for the request (10 seconds) + timeoutRef.current = setTimeout(() => { + cleanup(callbackName, scriptId); + setStatus("Error"); + setMsg("Request timed out. Please try again."); + }, 10000); + + // Define the callback function + window[callbackName] = (response) => { + // Handle Mailchimp's specific response format + if (response.result === "success") { + setStatus("Success"); + setMsg("Thank you for subscribing!"); + reCaptchaRef.current?.reset(); + setToken(null); + setEmail(""); // Clear email on success + } else if (response.msg && response.msg.toLowerCase().includes("already subscribed")) { + setStatus("Success"); + setMsg("You're already subscribed to our newsletter!"); + reCaptchaRef.current?.reset(); + setToken(null); + } else { + setStatus("Error"); + // Handle Mailchimp's error messages more gracefully + const errorMsg = response.msg || "An error occurred. Please try again."; + setMsg(errorMsg.replace(/<(?:.|\n)*?>/gm, "")); // Strip HTML from error message + } + cleanup(callbackName, scriptId); + }; + + // Create and append the script element + try { + const script = document.createElement("script"); + script.id = scriptId; + script.onerror = () => { + cleanup(callbackName, scriptId); + setStatus("Error"); + setMsg("Failed to connect to the subscription service. Please try again."); + }; + + // Construct Mailchimp URL with all required parameters + const params = new URLSearchParams({ + u: "cde2461ba84f5279fff352829", // User ID + id: "8d165e36d3", // List ID + EMAIL: email, + "group[57543][1]": "1", // Group selection + c: callbackName, // JSONP callback name + "g-recaptcha-response": token, + b_cde2461ba84f5279fff352829_8d165e36d3: "", // Bot prevention honeypot field + }); + + script.src = `https://us6.list-manage.com/subscribe/post-json?${params.toString()}`; + document.body.appendChild(script); + } catch (error) { + cleanup(callbackName, scriptId); + setStatus("Error"); + setMsg("An unexpected error occurred. Please try again."); + } + }; + + return ( +
+
+ +
+ + +
- return ( -
- - -
- - -
- - - Subscribe - -
- {siteKey && ( - - - - )} - {captchaError && ( - - - {captchaError} - - - )} - {status === "Error" && ( - - - {msg} - - - )} - {status === "Success" && ( - - - {msg} - - - )} - -
- ); + + {isSubmitting ? "Subscribing..." : "Subscribe"} + +
+ {siteKey && ( + + + + )} + {captchaError && ( + + + {captchaError} + + + )} + {status === "Error" && ( + + + {msg} + + + )} + {status === "Success" && ( + + + {msg} + + + )} + +
+ ); }; export default Newsletter;