Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test integration #24

Merged
merged 1 commit into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Loading