From d7a09635fd800af0f3d67a48063d5ac6729b230a Mon Sep 17 00:00:00 2001
From: ks129 <45097959+ks129@users.noreply.github.com>
Date: Tue, 23 Feb 2021 16:38:24 +0200
Subject: Implement hCaptcha to form page

---
 src/pages/FormPage.tsx | 50 ++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 48 insertions(+), 2 deletions(-)

(limited to 'src/pages/FormPage.tsx')

diff --git a/src/pages/FormPage.tsx b/src/pages/FormPage.tsx
index 1e331b9..9bd90d3 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";
 
@@ -154,6 +155,14 @@ const closedHeaderStyles = css`
   }
 `;
 
+const captchaStyles = css`
+  text-align: center;
+  
+  @media (max-width: 850px) {
+    padding: 1.2rem;
+  }
+`;
+
 function FormPage(): JSX.Element {
     const { id } = useParams<PathParams>();
 
@@ -161,6 +170,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);
@@ -208,6 +220,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) => {
@@ -242,6 +257,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 } = {};
@@ -276,7 +296,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);
     }
@@ -288,6 +314,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)) {
+        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}/>
@@ -296,6 +341,7 @@ function FormPage(): JSX.Element {
                 <form id="form" onSubmit={handleSubmit} css={[formStyles, unselectable]}>
                     { closed_header }
                     { questions }
+                    { captcha }
                 </form>
                 <Navigation form_state={open}/>
             </div>
-- 
cgit v1.2.3

