Skip to content

Commit

Permalink
Merge pull request #24 from celestiaorg/hotfix/newsletter-integration
Browse files Browse the repository at this point in the history
Test integration
  • Loading branch information
gabros20 authored Jan 8, 2025
2 parents 526613f + bad38f4 commit 0cd5e56
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 160 deletions.
66 changes: 33 additions & 33 deletions package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
301 changes: 174 additions & 127 deletions src/components/Newsletter/Newsletter.js
Original file line number Diff line number Diff line change
@@ -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 (
<div className={"modal-content-inner"}>
<form onSubmit={handleSubmit} className={"w-full"}>
<Row className='flex items-center gap-4'>
<div className={"w-full relative"}>
<label
htmlFor={"email"}
className={`px-2 py-3 absolute text-sm leading-[1.2857] transition-all ${
email.length > 0 ? "-top-8 -left-2 text-opacity-100" : "top-0 left-0 text-opacity-60 pointer-events-none"
}`}
>
Email
</label>
<input
type='email'
id={"email"}
value={email}
className={`w-full px-2 py-3 text-sm leading-[1.2857] bg-transparent border-b rounded-none ${
captchaError ? "border-red-error-subtle" : "border-white"
}`}
onChange={handleChange}
required
disabled={isSubmitting}
/>
</div>

return (
<div className={"modal-content-inner"}>
<form onSubmit={handleSubmit} className={"w-full"}>
<Row className="flex gap-4 items-center">
<div className={"w-full relative"}>
<label
htmlFor={"email"}
className={`px-2 py-3 absolute text-sm leading-[1.2857] transition-all ${
email.length > 0
? "-top-8 -left-2 text-opacity-100"
: "top-0 left-0 text-opacity-60"
}`}
>
Email
</label>
<input
type="email"
id={"email"}
className={`w-full px-2 py-3 text-sm leading-[1.2857] bg-transparent border-b rounded-none ${
captchaError ? "border-red-error-subtle" : "border-white"
}`}
onChange={handleChange}
required
/>
</div>

<PrimaryButton
lightMode
hover
className={"bg-white grow-0 shrink-0"}
type={"submit"}
onClick={handleSubmit}
>
Subscribe
</PrimaryButton>
</Row>
{siteKey && (
<Row className="mt-3">
<ReCAPTCHA
sitekey={siteKey}
ref={reCaptchaRef}
onChange={onReCAPTCHAChange}
asyncScriptOnLoad={asyncScriptOnLoad}
/>
</Row>
)}
{captchaError && (
<Row className="mt-2 px-2">
<Body size={"sm"} className={"text-red-error"}>
{captchaError}
</Body>
</Row>
)}
{status === "Error" && (
<Row className="mt-2 px-2">
<Body size={"sm"} className={"text-red-error"}>
{msg}
</Body>
</Row>
)}
{status === "Success" && (
<Row className="mt-2 px-2">
<Body size={"sm"} className={"text-green"}>
{msg}
</Body>
</Row>
)}
</form>
</div>
);
<PrimaryButton lightMode hover className={"bg-white grow-0 shrink-0 select-none"} type={"submit"} disabled={isSubmitting}>
{isSubmitting ? "Subscribing..." : "Subscribe"}
</PrimaryButton>
</Row>
{siteKey && (
<Row className='mt-3'>
<ReCAPTCHA sitekey={siteKey} ref={reCaptchaRef} onChange={onReCAPTCHAChange} />
</Row>
)}
{captchaError && (
<Row className='px-2 mt-2'>
<Body size={"sm"} className={"text-red-error"}>
{captchaError}
</Body>
</Row>
)}
{status === "Error" && (
<Row className='px-2 mt-2'>
<Body size={"sm"} className={"text-red-error"}>
{msg}
</Body>
</Row>
)}
{status === "Success" && (
<Row className='px-2 mt-2'>
<Body size={"sm"} className={"text-green"}>
{msg}
</Body>
</Row>
)}
</form>
</div>
);
};

export default Newsletter;

0 comments on commit 0cd5e56

Please sign in to comment.