diff --git a/package-lock.json b/package-lock.json index 1af4bbc22..579bf77e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13597,6 +13597,15 @@ "prop-types": "^15.6.2" } }, + "react-async-script": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/react-async-script/-/react-async-script-1.2.0.tgz", + "integrity": "sha512-bCpkbm9JiAuMGhkqoAiC0lLkb40DJ0HOEJIku+9JDjxX3Rcs+ztEOG13wbrOskt3n2DTrjshhaQ/iay+SnGg5Q==", + "requires": { + "hoist-non-react-statics": "^3.3.0", + "prop-types": "^15.5.0" + } + }, "react-dev-utils": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-4.2.3.tgz", @@ -13915,6 +13924,15 @@ "resolved": "https://registry.npmjs.org/react-ga/-/react-ga-2.7.0.tgz", "integrity": "sha512-AjC7UOZMvygrWTc2hKxTDvlMXEtbmA0IgJjmkhgmQQ3RkXrWR11xEagLGFGaNyaPnmg24oaIiaNPnEoftUhfXA==" }, + "react-google-recaptcha": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-2.1.0.tgz", + "integrity": "sha512-K9jr7e0CWFigi8KxC3WPvNqZZ47df2RrMAta6KmRoE4RUi7Ys6NmNjytpXpg4HI/svmQJLKR+PncEPaNJ98DqQ==", + "requires": { + "prop-types": "^15.5.0", + "react-async-script": "^1.1.1" + } + }, "react-helmet-async": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.0.5.tgz", diff --git a/package.json b/package.json index 416124862..c8b2d9729 100755 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "react": "^16.13.1", "react-dom": "^16.13.1", "react-ga": "^2.7.0", + "react-google-recaptcha": "^2.1.0", "react-icons": "^3.10.0", "react-syntax-highlighter": "^12.2.1", "react-transition-group": "^4.3.0", diff --git a/resources/content/articles/en/2020-05-04_04-00-00_faucet-en.md b/resources/content/articles/en/2020-05-04_04-00-00_faucet-en.md index 46fc4d657..b9aab6243 100644 --- a/resources/content/articles/en/2020-05-04_04-00-00_faucet-en.md +++ b/resources/content/articles/en/2020-05-04_04-00-00_faucet-en.md @@ -25,6 +25,6 @@ You access the testnet faucet using a form below, where you need to enter an add 1. Enter the address you want to top up in the field below. 2. Click 'Request'. 3. Funds will be accessible in 5-10 minutes. -4. When you have finished using your test tokens, please return them to the faucet so that other members of the community can use them. Please return your test tokens to this address: **ca1s44h5hytev8d9wty220ckglvj7ewm3yetyug8a29pu2lj5dff28sy2yf8yj** +4. When you have finished using your test tokens, please return them to the faucet so that other members of the community can use them. Please return your test tokens to this address: **ca1qhnyzcajgahztg0v6ngvn8jjud46pj8xcl82z279qe0hlkq43chgz2jccha** diff --git a/resources/content/global/global-en.md b/resources/content/global/global-en.md index 93dcc5423..654ae5eb1 100644 --- a/resources/content/global/global-en.md +++ b/resources/content/global/global-en.md @@ -34,6 +34,7 @@ content: success_heading: Success verify_transaction_hash: 'Please verify the following transaction hash:' transaction_successful: Your transaction has been successful and __{{ amount }}__ have been sent to __{{ address }}__. + please_complete_recaptcha: Please complete the ReCAPTCHA downloaders_content: version: Version sha_checksum: SHA256 checksum diff --git a/src/components/Faucet.js b/src/components/Faucet.js index 423b5aad5..38a4d439c 100644 --- a/src/components/Faucet.js +++ b/src/components/Faucet.js @@ -1,4 +1,4 @@ -import React, { useState, Fragment } from 'react' +import React, { useState, Fragment, useEffect, useRef } from 'react' import PropTypes from 'prop-types' import styled from 'styled-components' import Box from '@material-ui/core/Box' @@ -9,6 +9,7 @@ import CircularProgress from '@material-ui/core/CircularProgress' import Markdown from '@input-output-hk/front-end-core-components/components/Markdown' import Link from '@input-output-hk/front-end-core-components/components/Link' import moment from 'moment' +import ReCaptcha from 'react-google-recaptcha' import GlobalContentQuery from '../queries/GlobalContentQuery' const Container = styled(Box)` @@ -29,12 +30,14 @@ const LoadingContainer = styled.div` const DEFAULT_VALUES = { address: '', - apiKey: '' + apiKey: '', + reCaptcha: false } const DEFAULT_ERRORS = { address: '', - apiKey: '' + apiKey: '', + reCaptcha: '' } const statuses = { @@ -43,15 +46,21 @@ const statuses = { success: 'success' } -const FaucetInner = ({ content, getEndpoint, hasApiKey, getTransactionURL }) => { +const FaucetInner = ({ content, getEndpoint, hasApiKey, getTransactionURL, reCaptcha }) => { const [ values, setValues ] = useState(DEFAULT_VALUES) const [ errors, setErrors ] = useState(DEFAULT_ERRORS) const [ serverError, setServerError ] = useState('') const [ result, setResult ] = useState(null) const [ status, setStatus ] = useState(statuses.ready) + const reCaptchaRef = useRef(null) const valueOnChange = (key) => (e) => { - setValues({ ...values, [key]: e.target.value }) + if (key === 'reCaptcha') { + setValues({ ...values, [key]: e }) + } else { + setValues({ ...values, [key]: e.target.value }) + } + setErrors({ ...errors, [key]: '' }) } @@ -72,6 +81,7 @@ const FaucetInner = ({ content, getEndpoint, hasApiKey, getTransactionURL }) => const onSubmit = async (e) => { e.preventDefault() const newErrors = {} + if (!values.reCaptcha) newErrors.reCaptcha = content.faucet_content.please_complete_recaptcha if (Object.keys(newErrors).length > 0) { setErrors(newErrors) return @@ -81,16 +91,14 @@ const FaucetInner = ({ content, getEndpoint, hasApiKey, getTransactionURL }) => setServerError('') setStatus(statuses.loading) try { - const url = getEndpoint({ address: values.address, apiKey: values.apiKey }) + const endpointParams = { address: values.address, apiKey: values.apiKey } + if (reCaptcha) endpointParams.reCaptchaResponse = values.reCaptcha + const url = getEndpoint(endpointParams) const result = await fetch(url, { method: 'POST' }) const jsonResult = await result.json() if (result.status === 200 && jsonResult.success === false) { - if (jsonResult.txid === 'ERROR') { - setErrors({ ...errors, address: content.faucet_content.invalid_address }) - } else { - setServerError(content.faucet_content.server_error) - } - + if (jsonResult.txid === 'ERROR') setErrors({ ...errors, address: content.faucet_content.invalid_address }) + setServerError(content.faucet_content.server_error) setStatus(statuses.ready) } else if (result.status === 200) { setResult({ txid: jsonResult.txid, amount: jsonResult.amount }) @@ -129,6 +137,13 @@ const FaucetInner = ({ content, getEndpoint, hasApiKey, getTransactionURL }) => setResult(null) } + useEffect(() => { + if (reCaptchaRef.current && serverError) { + reCaptchaRef.current.reset() + setValues({ ...values, reCaptcha: null }) + } + }, [ reCaptchaRef, serverError ]) + return ( {[ statuses.ready, statuses.loading ].includes(status) && @@ -169,6 +184,20 @@ const FaucetInner = ({ content, getEndpoint, hasApiKey, getTransactionURL }) => /> } + {reCaptcha && + + {errors.reCaptcha && + + {errors.reCaptcha} + + } + + + }