diff options
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | src/pages/FormPage.tsx | 48 | ||||
-rw-r--r-- | webpack.config.js | 3 | ||||
-rw-r--r-- | yarn.lock | 12 |
4 files changed, 62 insertions, 3 deletions
diff --git a/package.json b/package.json index 2e5bf34..eeb8983 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@fortawesome/free-brands-svg-icons": "5.15.2", "@fortawesome/free-solid-svg-icons": "5.15.2", "@fortawesome/react-fontawesome": "0.1.14", + "@hcaptcha/react-hcaptcha": "0.3.2", "@sentry/react": "6.2.1", "@svgr/webpack": "5.5.0", "@swc/core": "1.2.50", @@ -59,6 +60,7 @@ "@testing-library/jest-dom": "5.11.9", "@testing-library/react": "11.2.5", "@testing-library/user-event": "12.8.1", + "@types/hcaptcha__react-hcaptcha": "0.1.4", "@types/jest": "26.0.20", "@types/node": "14.14.31", "@types/react": "17.0.2", diff --git a/src/pages/FormPage.tsx b/src/pages/FormPage.tsx index 8852ac5..2022d43 100644 --- a/src/pages/FormPage.tsx +++ b/src/pages/FormPage.tsx @@ -2,7 +2,8 @@ import { jsx, css } from "@emotion/react"; import { Link } from "react-router-dom"; -import React, { SyntheticEvent, useEffect, useState, createRef } from "react"; +import HCaptcha from "@hcaptcha/react-hcaptcha"; +import React, {SyntheticEvent, useEffect, useState, createRef, useRef} from "react"; import { useParams } from "react-router"; import { PropagateLoader } from "react-spinners"; @@ -143,6 +144,12 @@ const closedHeaderStyles = css` } `; +const captchaStyles = css` + text-align: center; + + margin-bottom: 1.5rem; +`; + function FormPage(): JSX.Element { const { id } = useParams<PathParams>(); @@ -150,6 +157,9 @@ function FormPage(): JSX.Element { const [sending, setSending] = useState<boolean>(); const [sent, setSent] = useState<boolean>(); + let captchaToken: string | null = null; + const captchaRef = useRef<HCaptcha>(null); + useEffect(() => { getForm(id).then(form => { setForm(form); @@ -197,6 +207,9 @@ function FormPage(): JSX.Element { async function handleSubmit(event: SyntheticEvent) { event.preventDefault(); + // Make copy to avoid losing value on state change. + const submitCaptchaToken = captchaToken; + // Client-side required validation const invalidFieldIDs: number[] = []; questions.forEach((prop, i) => { @@ -231,6 +244,11 @@ function FormPage(): JSX.Element { return; } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + if (!(FormFeatures.DisableAntispam in form!.features) && !submitCaptchaToken && captchaRef && captchaRef.current) { + captchaRef.current.execute(); + } + setSending(true); const answers: { [key: string]: unknown } = {}; @@ -265,7 +283,13 @@ function FormPage(): JSX.Element { } }); - await ApiClient.post(`forms/submit/${id}`, {response: answers}); + const data: { [key: string]: unknown } = {response: answers}; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + if (!(FormFeatures.DisableAntispam in form!.features)) { + data["captcha"] = submitCaptchaToken; + } + + await ApiClient.post(`forms/submit/${id}`, data); setSending(false); setSent(true); } @@ -284,6 +308,25 @@ function FormPage(): JSX.Element { closed_header = <div css={closedHeaderStyles}>This form is now closed. You will not be able to submit your response.</div>; } + let captcha = null; + if (!(FormFeatures.DisableAntispam in form.features) && open) { + captcha = ( + <div css={captchaStyles}> + <HCaptcha + sitekey={process.env.HCAPTCHA_SITEKEY ? process.env.HCAPTCHA_SITEKEY : ""} + theme="dark" + onVerify={token => { + captchaToken = token; + }} + onExpire={() => { + captchaToken = null; + }} + ref={captchaRef} + /> + </div> + ); + } + return ( <div> <HeaderBar title={form.name} description={form.description}/> @@ -293,6 +336,7 @@ function FormPage(): JSX.Element { { closed_header } { questions } </form> + { captcha } <Navigation form_state={open} scopes={scopes}/> </div> diff --git a/webpack.config.js b/webpack.config.js index 35c90a7..70c2ae9 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -93,7 +93,8 @@ module.exports = { REACT_APP_SENTRY_DSN: "https://[email protected]/1234", REACT_APP_OAUTH2_CLIENT_ID: "0", BACKEND_URL: "https://forms-api.pythondiscord.com/", - CONTEXT: "development" + CONTEXT: "development", + HCAPTCHA_SITEKEY: "10000000-ffff-ffff-ffff-000000000001" }), new HtmlWebpackPlugin({ inject: true, template: 'public/index.html' @@ -1295,6 +1295,11 @@ dependencies: "@hapi/hoek" "^8.3.0" +"@hcaptcha/[email protected]": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@hcaptcha/react-hcaptcha/-/react-hcaptcha-0.3.2.tgz#8910ea4c111799336fb64de6aa7b74329a7d7579" + integrity sha512-+90hSDwtnKAk3PXJsyABi+wRHS1B+wgWjDO4nz68KpkLnE73rMz/XMdl+ckrwYkFFilzIDKI3o1IqOpMapEwgg== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -2015,6 +2020,13 @@ dependencies: "@types/node" "*" +"@types/[email protected]": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@types/hcaptcha__react-hcaptcha/-/hcaptcha__react-hcaptcha-0.1.4.tgz#145cab4f0ac29fe8925dc98ab16cf2dffacad7d5" + integrity sha512-eqEIBR7yn4Y1fRtxPnFlEP8SAHwX762Z27s/ifd5wfJicviz6HynF8gHCgUPPtfYlaQFHa/A0NkwBA5YNmNCmQ== + dependencies: + "@types/react" "*" + "@types/history@*": version "4.7.8" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" |