Skip to content

Commit

Permalink
Merge pull request #238 from sanctuarycomputer/feature/add-recaptcha
Browse files Browse the repository at this point in the history
Add Google ReCAPTCHA v2 Checkbox to Request Pricing Info modal
  • Loading branch information
mokaymokay authored Aug 11, 2023
2 parents 6fdfc21 + f891821 commit 4dddecf
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 44 deletions.
142 changes: 100 additions & 42 deletions lib/ModalContext.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { createContext, useState } from 'react';
import { createContext, useState, useRef } from 'react';
import Modal from 'react-modal';
import ReCAPTCHA from 'react-google-recaptcha';
import recordConversion from 'utils/recordConversion';
import validateEmail from 'utils/validateEmail';

const customStyles = {
overlay: {
Expand All @@ -12,7 +14,7 @@ const customStyles = {
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
margin: '0 auto',
transform: 'translate(-50%, -50%)',
border: 'none',
},
Expand All @@ -23,17 +25,52 @@ const ModalContext = createContext({
});

const ModalProvider = ({ children }) => {
const reCaptchaRef = useRef(null);

const [currentModal, setCurrentModal] = useState(null);
const modalIsOpen = currentModal !== null;
const closeModal = () => setCurrentModal(null);

const [email, setEmail] = useState(null);
const [reCaptchaValidated, setReCaptchaValidated] = useState(false);
const [emailError, setEmailError] = useState('');
const [submitting, setSubmitting] = useState(false);

const closeModal = () => {
setCurrentModal(null);
setEmail(null);
setSubmitting(false);
setEmailError('');
};

const onReCaptchaSuccess = async (captchaCode) => {
if (!captchaCode) {
return;
}

setReCaptchaValidated(true);
};

const onBlur = () => {
if (!email) {
setEmailError('Please enter your email address');
} else if (!validateEmail(email)) {
setEmailError('Invalid email address');
} else {
setEmailError('');
}
};

const onSubmit = async (e) => {
e.preventDefault();

if (!validateEmail(email) || !reCaptchaValidated) {
return;
}

recordConversion();
setSubmitting(true);

try {
setSubmitting(true);
await fetch('/api/emails', {
method: 'POST',
redirect: 'follow',
Expand All @@ -43,61 +80,82 @@ const ModalProvider = ({ children }) => {
},
body: JSON.stringify({ email }),
});
} finally {
setSubmitting(false);
reCaptchaRef.current.reset();

const url = new URL(window.location.href);
url.searchParams.set('note', 'thankyou');
window.location.replace(url.toString());
} finally {
setSubmitting(false);
}
};

return (
<ModalContext.Provider value={{ setCurrentModal }}>
{children}

{currentModal === 'requestPricingInfo' && (
<Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
style={customStyles}
contentLabel="Request Pricing Info Modal"
>
<div className="text-right mb1">
<button
className="SignUpForm__button p0 m0 small pointer"
onClick={closeModal}
>
×
</button>
</div>
<form action="/api/emails" method="post" onSubmit={onSubmit}>
<label className="small">
<h3 className="mb1">
<>
<ModalContext.Provider value={{ setCurrentModal }}>
{children}

{currentModal === 'requestPricingInfo' && (
<Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
style={customStyles}
contentLabel="Request Pricing Info Modal"
>
<div className="text-right mb1">
<button
className="SignUpForm__button SignUpForm__button--close p0 m0 small pointer"
aria-label="Close modal"
onClick={closeModal}
>
×
</button>
</div>
<form action="/api/emails" method="post" onSubmit={onSubmit}>
<label htmlFor="email" className="small">
Submit your email below and we&apos;ll send over our pricing
information.
</h3>
</label>
<input
id="email"
name="email"
autoFocus
value={email}
aria-describedby="email_error"
onBlur={onBlur}
onChange={(e) => setEmail(e.target.value)}
className="SignUpForm__input SignUpForm--day-mode__input small p0 w100 inline-block"
className="SignUpForm__input SignUpForm--day-mode__input small p0 w100 mt1 inline-block"
type="email"
placeholder="[email protected]"
/>
</label>
<div>
<input
className="SignUpForm__button p0 m0 small"
type="submit"
value={submitting ? 'Submitting...' : 'Submit'}
disabled={submitting}
/>
</div>
</form>
</Modal>
)}
</ModalContext.Provider>
{emailError && (
<span
id="email_error"
className="SignUpForm__error tiny"
aria-live="assertive"
>
{emailError}
</span>
)}
<div>
<ReCAPTCHA
ref={reCaptchaRef}
className="SignUpForm__reCaptcha pt1"
sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY}
onChange={onReCaptchaSuccess}
/>
<input
className="SignUpForm__button p0 small"
type="submit"
value={submitting ? 'Submitting...' : 'Submit'}
disabled={submitting || !reCaptchaValidated}
/>
</div>
</form>
</Modal>
)}
</ModalContext.Provider>
</>
);
};

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"react": "17.0.2",
"react-calendly": "^4.1.1",
"react-dom": "17.0.2",
"react-google-recaptcha": "^3.1.0",
"react-modal": "^3.16.1",
"react-remarkable": "^1.1.3",
"react-swipeable": "^6.2.0",
Expand Down
18 changes: 18 additions & 0 deletions styles/components/SignUpForm.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@
background-color: transparent;
}

&__button:disabled {
color: color('gray');
cursor: not-allowed;
}

&__error {
color: color('error');
}

&__button--close {
color: color('black');
}

&__reCaptcha {
transform: scale(0.85);
transform-origin: 0 0;
}

&--overlay-mode {
&__input,
&__button {
Expand Down
1 change: 1 addition & 0 deletions styles/config.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ $colors: (
'gray-darkest': #141414,
'gray': #757575,
'white': #ffffff,
'error': #900,
);

$breakpoints: (
Expand Down
7 changes: 7 additions & 0 deletions utils/validateEmail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

const validateEmail = (email) => {
return emailRegex.test(email);
};

export default validateEmail;
27 changes: 25 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2519,6 +2519,13 @@ hmac-drbg@^1.0.1:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"

hoist-non-react-statics@^3.3.0:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
dependencies:
react-is "^16.7.0"

http-cache-semantics@^4.0.0:
version "4.1.0"
resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz"
Expand Down Expand Up @@ -4025,7 +4032,7 @@ promise-inflight@^1.0.1:
resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz"
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=

prop-types@^15.7.2, prop-types@^15.8.1:
prop-types@^15.5.0, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
Expand Down Expand Up @@ -4164,6 +4171,14 @@ rc@^1.2.8:
minimist "^1.2.0"
strip-json-comments "~2.0.1"

react-async-script@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/react-async-script/-/react-async-script-1.2.0.tgz#ab9412a26f0b83f5e2e00de1d2befc9400834b21"
integrity sha512-bCpkbm9JiAuMGhkqoAiC0lLkb40DJ0HOEJIku+9JDjxX3Rcs+ztEOG13wbrOskt3n2DTrjshhaQ/iay+SnGg5Q==
dependencies:
hoist-non-react-statics "^3.3.0"
prop-types "^15.5.0"

react-calendly@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/react-calendly/-/react-calendly-4.1.1.tgz#25ac2ff3e062e4c6a0aefb83b851bebf66a94a1c"
Expand All @@ -4178,7 +4193,15 @@ [email protected]:
object-assign "^4.1.1"
scheduler "^0.20.2"

react-is@^16.13.1:
react-google-recaptcha@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/react-google-recaptcha/-/react-google-recaptcha-3.1.0.tgz#44aaab834495d922b9d93d7d7a7fb2326315b4ab"
integrity sha512-cYW2/DWas8nEKZGD7SCu9BSuVz8iOcOLHChHyi7upUuVhkpkhYG/6N3KDiTQ3XAiZ2UAZkfvYKMfAHOzBOcGEg==
dependencies:
prop-types "^15.5.0"
react-async-script "^1.2.0"

react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
Expand Down

2 comments on commit 4dddecf

@vercel
Copy link

@vercel vercel bot commented on 4dddecf Aug 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for sanctu-dot-com ready!

✅ Preview
https://sanctu-dot-pbpha7nf5-sanctucompu.vercel.app

Built with commit 4dddecf.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.