UNPKG

@proca/widget

Version:

Proca is an open-source campaign toolkit designed to empower activists and organisations in their digital advocacy efforts. It provides a flexible and customisable platform for creating and managing online petitions, email campaigns, and other forms of di

242 lines (226 loc) 7.16 kB
import React, { useState, useEffect } from "react"; //import parse from 'html-react-parser'; import { Grid, Box, Button } from "@material-ui/core"; import TextField from "@components/TextField"; import { useTranslation } from "react-i18next"; import IconButton from "@material-ui/core/IconButton"; import PlayIcon from "@material-ui/icons/RecordVoiceOver"; import PlayCircleOutlineIcon from "@material-ui/icons/PlayCircleOutline"; import InputAdornment from "@material-ui/core/InputAdornment"; import ReplayIcon from "@material-ui/icons/Replay"; import { makeStyles } from "@material-ui/core/styles"; import dispatch from "@lib/event"; import { useCampaignConfig } from "@hooks/useConfig"; const useStyles = makeStyles(theme => ({ focus: { "& svg": { backgroundColor: "rgba(0, 0, 0, 0.09)", transition: theme.transitions.create(["background-color", "transform"], { duration: theme.transitions.duration.complex, }), "& path.n": { strokeWidth: 2, filter: "none", fill: "none", stroke: "rgba(0, 0, 0, 0.13)", }, }, }, captcha: { backgroundColor: theme.palette.background.paper, "& path": { stroke: theme.palette.primary.light, fill: theme.palette.primary.main, filter: "drop-shadow( 2px 1px 1px rgba(0, 0, 0, .4))", }, "& path.n": { strokeWidth: 3, filter: "none", fill: "none", stroke: theme.palette.primary.main, }, }, })); export default function Captcha(props) { const config = useCampaignConfig(); const [captcha, setCaptcha] = useState(null); const [isFocussed, setFocussed] = useState(false); const classes = useStyles(); const [count, setCount] = useState(0); const [audioCaptcha, _setAudioCaptcha] = useState(false); const withAudioCaptcha = config.component.captcha?.audio !== false; const { t } = useTranslation(); const { setValue, formState: { errors }, } = props.form; const compact = props.compact || false; const setAudioCaptcha = audio => { _setAudioCaptcha(audio); props.onChange({ ...captcha, audio: audio }); }; const update = captcha => { setCaptcha(captcha); captcha.count = count; props.onChange(captcha); }; useEffect(() => { if (errors.captcha) { dispatch("input_error", { type: "captcha", count: count + 1, message: errors.captcha.message, }); } if (!errors.captcha) return; // setCount((c) => c + 1); setFocussed(true); // setValue("captcha", ""); }, [errors.captcha]); useEffect(() => { let isLive = true; (async () => { fetch(config.component.eci?.captcha || "https://captcha.proca.app") .then(response => response.json()) .then(captcha => isLive && update(captcha)); })(); return () => (isLive = false); }, [count]); const handleClick = () => { setCount(count + 1); setFocussed(true); setValue("captcha", ""); // setValues({ ...values, showPassword: !values.showPassword }); }; const handleMouseOver = () => { setFocussed(true); }; const handleMouseLeave = () => { setFocussed(false); }; const handleMouseDown = event => { event.preventDefault(); }; const handleFocus = () => { setFocussed(true); }; const handleBlur = () => { setFocussed(false); }; const Svg = () => { if (!captcha) return null; return ( <svg className={classes.captcha} viewBox={`0,0,${captcha.width},${captcha.height + 17}`} > <title>captcha</title> {captcha.d.map((d, i) => ( <path className={d.startsWith("M19 84") ? "n" : null} key={i} d={d} /> ))} </svg> ); }; // return parse(captcha.data); const handlePlay = lang => { const d = captcha.mac .substr(captcha.expiry % 10, 4) .split("") .join(" ") .toLowerCase(); const utterThis = new SpeechSynthesisUtterance(d); utterThis.rate = 0.9; utterThis.lang = lang || "en"; window.speechSynthesis && window.speechSynthesis.speak(utterThis); }; return ( <> <Grid container spacing={1}> {audioCaptcha && ( <Grid item xs={12}> <Grid> <Button variant="contained" onClick={() => setAudioCaptcha(false)} size="small" aria-label={t("eci:form.captcha-button-arialabel-image")} > {t("eci:form.captcha-button-arialabel-image", "image 🖼")} </Button> </Grid> <TextField form={props.form} label={t("eci:form.captcha-audio-download")} name="captcha" required InputProps={{ "aria-label": t("eci:form.captcha-audio-download"), endAdornment: ( <InputAdornment position="end"> <IconButton aria-label={t( "eci:form.captcha-button-arialabel-refresh" )} onClick={() => handlePlay(config.lang)} > <PlayCircleOutlineIcon /> </IconButton> </InputAdornment> ), }} /> </Grid> )} {!audioCaptcha && ( <> <Grid item xs={compact ? 12 : 5}> <TextField form={props.form} name="captcha" label="Captcha" helperText={errors.captcha && t("eci:form.captcha-image-title")} onFocus={handleFocus} onBlur={handleBlur} onMouseOver={handleMouseOver} onMouseLeave={handleMouseLeave} InputProps={{ endAdornment: ( <InputAdornment position="end"> <IconButton aria-label={t( "eci:form.captcha-button-arialabel-refresh" )} onClick={handleClick} onMouseDown={handleMouseDown} onMouseOver={handleMouseOver} onMouseLeave={handleMouseLeave} > <ReplayIcon /> </IconButton> </InputAdornment> ), }} required /> </Grid> <Grid item xs={compact ? 10 : 5}> <Box py={1} className={isFocussed ? classes.focus : null}> <Svg /> </Box> </Grid> {withAudioCaptcha && ( <Grid item xs={2}> <IconButton aria-label={t("eci:form.captcha-button-arialabel-audio")} onClick={() => setAudioCaptcha(true)} > <PlayIcon /> </IconButton> </Grid> )} </> )} </Grid> </> ); }