Last active
July 22, 2022 07:48
-
-
Save bootrino/c993d98d11927158ba428dc79e2e6a0b to your computer and use it in GitHub Desktop.
How to do recaptcha with TypeScript.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, {useState, useEffect, useMemo} from "react"; | |
import styled, {keyframes} from "styled-components"; | |
import {toast} from "react-toastify"; | |
import {CloseIcon} from "../Icons"; | |
import {doFetch} from "../../utils"; | |
import {Button as BSButton} from 'react-bootstrap'; | |
import PhoneInput from 'react-phone-number-input' | |
import 'react-phone-number-input/style.css' | |
import {debounce} from "lodash"; | |
const openModal = keyframes` | |
from { | |
opacity: 0; | |
} | |
to { | |
opacity: 1; | |
} | |
`; | |
const Wrapper = styled.div` | |
position: fixed; | |
left: 0; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
z-index: 900; | |
background: rgba(0, 0, 0, 0.7); | |
animation: ${openModal} 0.5s ease-in-out; | |
.edit-profile { | |
width: 580px; | |
border-radius: 4px; | |
background: ${(props) => props.theme.grey}; | |
margin: 36px auto; | |
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.4), 0px 0px 4px rgba(0, 0, 0, 0.25); | |
} | |
.edit-profile img { | |
object-fit: cover; | |
} | |
.avatar { | |
margin-top: -40px; | |
margin-left: 20px; | |
} | |
div.modal-header { | |
display: flex; | |
align-items: center; | |
justify-content: space-between; | |
padding: 1rem; | |
border-bottom: 1px solid ${(props) => props.theme.darkGrey}; | |
} | |
h3 { | |
display: flex; | |
align-items: center; | |
} | |
form { | |
padding: 1rem; | |
} | |
input, | |
textarea { | |
width: 100%; | |
background: ${(props) => props.theme.black}; | |
border: 1px solid ${(props) => props.theme.black}; | |
padding: 0.6rem 1rem; | |
border-radius: 3px; | |
color: ${(props) => props.theme.primaryColor}; | |
} | |
textarea { | |
height: 75px; | |
} | |
svg { | |
fill: ${(props) => props.theme.red}; | |
height: 22px; | |
width: 22px; | |
margin-right: 1rem; | |
position: relative; | |
top: -1px; | |
} | |
@media screen and (max-width: 600px) { | |
.edit-profile { | |
width: 90%; | |
margin: 4rem auto; | |
} | |
} | |
@media screen and (max-width: 400px) { | |
background: rgba(0, 0, 0, 0.9); | |
} | |
`; | |
function asyncDebounce(func: Function, wait: number) { | |
const debounced = debounce((resolve, reject, args) => { | |
func(...args).then(resolve).catch(reject); | |
}, wait); | |
return (...args: any[]) => | |
new Promise((resolve, reject) => { | |
debounced(resolve, reject, args); | |
}); | |
} | |
const checkNumber = async (phoneNumber: string): Promise<boolean> => { | |
const url = `https://validate-phonenumber-7503.twil.io/validate?phone=${phoneNumber}` | |
try { | |
const result = await fetch(url); | |
const data = await result.json() | |
return data.success | |
} catch (e) { | |
alert(e) | |
return false | |
} | |
} | |
const debouncedCheckNumber: (...args: any[]) => Promise<unknown> = asyncDebounce(checkNumber, 1000); | |
const VerifyPhonenumberModal = () => { | |
const RECAPTCHA_PUBLIC_FRONTEND = "GETTHISFROMGOOGLE"; | |
const [getPhoneNumberIsValid, setPhoneNumberIsValid] = useState<boolean>(false); | |
const [getCaptchaValue, setCaptchaValue] = useState<string | undefined>(undefined); | |
const [getPhoneNumber, setPhoneNumber] = useState<string>(""); | |
const [getShowModal, setShowModal] = useState<boolean>(false); | |
const grecaptcha_element = React.useRef<HTMLDivElement>(null) | |
const onSubmit = async () => { | |
const url = `/auth/verifyphonenumber/${getPhoneNumber}/${getCaptchaValue}` | |
const result = await doFetch(url) | |
console.log(await result.json()) | |
} | |
const closeModal = async () => { | |
setShowModal(false) | |
setPhoneNumber("") | |
setPhoneNumberIsValid(false) | |
setCaptchaValue("") | |
} | |
useEffect(() => { | |
// load recaptcha JS code | |
(window as any).captchaCallback = function (captchaValue: string) { | |
console.log('captchaCallback') | |
setCaptchaValue(captchaValue) | |
}; | |
(window as any).captchaExpiredCallback = function() { | |
console.log('captchaExpiredCallback') | |
setCaptchaValue(undefined) | |
}; | |
const scriptElement = document.createElement("script") | |
scriptElement.src = "https://www.google.com/recaptcha/api.js" | |
document.body.appendChild(scriptElement) as HTMLScriptElement | |
return () => { | |
document.body.removeChild(scriptElement); | |
(window as any).captchaCallback = undefined; | |
(window as any).captchaExpiredCallback = undefined; | |
} | |
}, []) | |
useEffect(() => { | |
const checkNumber = async () => { | |
const isValid = await debouncedCheckNumber(getPhoneNumber) as boolean | |
setPhoneNumberIsValid(isValid) | |
} | |
checkNumber() | |
}, [getPhoneNumber, setPhoneNumberIsValid]) | |
useEffect(() => { | |
const handler = (e: CustomEvent) => { | |
console.log(e) | |
setShowModal(true) | |
} | |
document.addEventListener('SHOW_VERIFY_PHONE_NUMBER_MODAL', handler as EventListener) | |
return () => document.removeEventListener('SHOW_VERIFY_PHONE_NUMBER_MODAL', handler as EventListener) | |
}, [setShowModal]) | |
useEffect(() => { | |
if (!grecaptcha_element) return | |
if (!grecaptcha_element.current) return | |
// we | |
// @ts-ignore | |
grecaptcha.render(grecaptcha_element.current, { | |
'sitekey': RECAPTCHA_PUBLIC_FRONTEND, | |
'callback': "captchaCallback", | |
'theme': "dark", | |
'expired-callback': "captchaExpiredCallback", | |
}); | |
}, [getShowModal]) | |
if (!getShowModal) return null | |
return ( | |
<Wrapper> | |
<div className="edit-profile"> | |
<div style={{paddingTop: "10px", paddingBottom: "10px",}}> | |
<div className="modal-header"> | |
<h3> | |
<CloseIcon onClick={closeModal}/> | |
<span>Verify Phone Number</span> | |
</h3> | |
</div> | |
<div style={{"textAlign": "center", "paddingTop": "1em", "marginBottom": "1em", "marginTop": "1em"}}> | |
<p>You must verify your phone number before you can render videos.</p> | |
</div> | |
<div className="container"> | |
<PhoneInput | |
international | |
defaultCountry="US" | |
placeholder="Enter phone number" | |
value={getPhoneNumber} | |
onChange={(value) => { | |
if (value !== undefined) setPhoneNumber(value) | |
}}/> | |
<div style={{"textAlign": "center"}}> | |
{getPhoneNumberIsValid && <img | |
src="/images/greentick.png" | |
style={{"width": "48px", "marginTop": "10px"}}/>} | |
{getPhoneNumberIsValid && <p>looks like a valid number... now hit "send verify code"</p>} | |
</div> | |
<div style={{"width": "100%", "display": "flex", "justifyContent": "center", "marginBottom": "1em", "marginTop": "1em"}}> | |
<div ref={grecaptcha_element}/> | |
</div> | |
<div style={{"width": "100%", "display": "flex", "justifyContent": "center", "marginBottom": "1em", "marginTop": "1em"}}> | |
<BSButton | |
className="btn btn-secondary" | |
onClick={closeModal}>cancel</BSButton> | |
| |
<BSButton | |
disabled={(getCaptchaValue === undefined) || (!getPhoneNumberIsValid)} | |
className={((getCaptchaValue === undefined) || (!getPhoneNumberIsValid)) ? "btn btn-outline-light" : "btn btn-primary"} | |
onClick={onSubmit}>send verify code</BSButton> | |
</div> | |
</div> | |
</div> | |
</div> | |
</Wrapper> | |
); | |
}; | |
export default VerifyPhonenumberModal; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment