diff options
| author | 2021-02-21 08:33:06 +0200 | |
|---|---|---|
| committer | 2021-02-21 08:33:06 +0200 | |
| commit | 75273e5798eb3cc69fb23ab8547b9a438d35845e (patch) | |
| tree | 47446ed79136e5c6b91af1284d34119bbb5c5df4 /src | |
| parent | Migrate from public state map to Redux (diff) | |
Implement basic hCaptcha
Diffstat (limited to 'src')
| -rw-r--r-- | src/pages/FormPage.tsx | 52 | ||||
| -rw-r--r-- | src/store/form/actions.ts | 14 | ||||
| -rw-r--r-- | src/store/form/reducers.ts | 7 | ||||
| -rw-r--r-- | src/store/form/types.ts | 3 |
4 files changed, 69 insertions, 7 deletions
diff --git a/src/pages/FormPage.tsx b/src/pages/FormPage.tsx index e9306e3..c06b33c 100644 --- a/src/pages/FormPage.tsx +++ b/src/pages/FormPage.tsx @@ -2,10 +2,11 @@ import { jsx, css } from "@emotion/react"; import { Link } from "react-router-dom"; -import React, { SyntheticEvent, useEffect, useState, createRef } from "react"; +import React, {SyntheticEvent, useEffect, useState, createRef, useRef} from "react"; import { useParams } from "react-router"; import { PropagateLoader } from "react-spinners"; import { useDispatch, useSelector } from "react-redux"; +import HCaptcha from "@hcaptcha/react-hcaptcha"; import HeaderBar from "../components/HeaderBar"; import ConnectedRenderedQuestion, { RenderedQuestion } from "../components/Question"; @@ -18,7 +19,7 @@ import { unselectable } from "../commonStyles"; import { Question, QuestionType } from "../api/question"; import ApiClient from "../api/client"; import { FormState } from "../store/form/types"; -import { clean } from "../store/form/actions"; +import { clean, setCaptchaToken } from "../store/form/actions"; interface PathParams { id: string @@ -157,6 +158,14 @@ const closedHeaderStyles = css` } `; +const captchaStyles = css` + text-align: center; + + @media (max-width: 850px) { + padding-bottom: 1.2rem; + } +`; + function FormPage(): JSX.Element { const { id } = useParams<PathParams>(); @@ -166,6 +175,9 @@ function FormPage(): JSX.Element { const values = useSelector<FormState, FormState["values"]>( state => state.values ); + const captchaToken = useSelector<FormState, FormState["captchaToken"]>( + state => state.captchaToken + ); const dispatch = useDispatch(); @@ -173,6 +185,9 @@ function FormPage(): JSX.Element { const [sending, setSending] = useState<boolean>(); const [sent, setSent] = useState<boolean>(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const captchaRef = useRef<any>(); + useEffect(() => { dispatch(clean()); getForm(id).then(form => { @@ -255,6 +270,14 @@ function FormPage(): JSX.Element { return; } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + if (!(FormFeatures.DisableAntispam in form!.features) && captchaToken === null) { + if (captchaRef && captchaRef.current) { + captchaRef.current.execute(); + } + return; + } + setSending(true); const answers: { [key: string]: unknown } = {}; @@ -294,7 +317,14 @@ 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"] = captchaToken; + } + + await ApiClient.post(`forms/submit/${id}`, data); setSending(false); setSent(true); } @@ -306,6 +336,21 @@ 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) => dispatch(setCaptchaToken(token))} + onExpire={() => dispatch(setCaptchaToken(null))} + ref={captchaRef} + /> + </div> + ); + } + return ( <div> <HeaderBar title={form.name} description={form.description}/> @@ -314,6 +359,7 @@ function FormPage(): JSX.Element { <form id="form" onSubmit={handleSubmit} css={[formStyles, unselectable]}> { closed_header } { questions } + { captcha } </form> <Navigation form_state={open}/> </div> diff --git a/src/store/form/actions.ts b/src/store/form/actions.ts index 8b48a37..dc43729 100644 --- a/src/store/form/actions.ts +++ b/src/store/form/actions.ts @@ -5,7 +5,8 @@ export enum FormAction { SET_VALUE = "SET_VALUE", SET_ERROR = "SET_ERROR", SET_VALID = "SET_VALID", - CLEAN = "CLEAN" + CLEAN = "CLEAN", + SET_CAPTCHA_TOKEN = "SET_CAPTCHA_TOKEN" } // This is base for all actions @@ -42,7 +43,12 @@ export interface CleanAction extends DefaultFormAction { type: FormAction.CLEAN } -export type Action = SetValueAction | SetErrorAction | SetValidAction | CleanAction; +export interface SetCaptchaTokenAction extends DefaultFormAction { + type: FormAction.SET_CAPTCHA_TOKEN, + payload: string | null +} + +export type Action = SetValueAction | SetErrorAction | SetValidAction | CleanAction | SetCaptchaTokenAction; export function setValue(question: Question, value: string | Map<string, boolean> | null): SetValueAction { return { @@ -77,3 +83,7 @@ export function setValid(question: Question, valid: boolean): SetValidAction { export function clean(): CleanAction { return { type: FormAction.CLEAN }; } + +export function setCaptchaToken(token: string | null): SetCaptchaTokenAction { + return { type: FormAction.SET_CAPTCHA_TOKEN, payload: token }; +} diff --git a/src/store/form/reducers.ts b/src/store/form/reducers.ts index fcbb33a..21bd047 100644 --- a/src/store/form/reducers.ts +++ b/src/store/form/reducers.ts @@ -4,7 +4,8 @@ import {FormState} from "./types"; export const initialState: FormState = { values: new Map(), errors: new Map(), - valid: new Map() + valid: new Map(), + captchaToken: null }; export function formReducer(state = initialState, action: Action): FormState { @@ -24,6 +25,10 @@ export function formReducer(state = initialState, action: Action): FormState { case FormAction.CLEAN: return initialState; + + case FormAction.SET_CAPTCHA_TOKEN: + new_state.captchaToken = action.payload; + break; } return new_state; } diff --git a/src/store/form/types.ts b/src/store/form/types.ts index 1d3e594..3f8557e 100644 --- a/src/store/form/types.ts +++ b/src/store/form/types.ts @@ -1,5 +1,6 @@ export interface FormState { values: Map<string, string | Map<string, boolean> | null>, errors: Map<string, string>, - valid: Map<string, boolean> + valid: Map<string, boolean>, + captchaToken: string | null } |