Skip to content

Commit

Permalink
feat: add hcaptcha integration
Browse files Browse the repository at this point in the history
  • Loading branch information
nzambello committed Aug 7, 2021
1 parent 5c0cbdb commit 3ae4599
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 93 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ For each field, you can select the field type from:
For every field you can set a label and a help text.
For select, radio and checkbox fields, you can select a list of values.

## Captcha verification

This form addon is configured to work with [HCaptch](https://www.hcaptcha.com) and [ReCaptch](https://www.google.com/recaptcha/) to prevent spam.
In order to make one of these integrations work, you need to add an enviroment variable with the key `RAZZLE_RECAPTCHA_KEY` or `RAZZLE_HCAPTCHA_KEY` and the value of the key in your `.env` file.

## Export

With backend support, you can store data submitted from the form.
Expand All @@ -59,7 +64,17 @@ config.blocks.blocksConfig.form.additionalFields.push({
The widget component should have the following firm:

```js
({ id, name, title, description, required, onChange, value, isDisabled, invalid }) => ReactElement;
({
id,
name,
title,
description,
required,
onChange,
value,
isDisabled,
invalid,
}) => ReactElement;
```

You should also pass a function to validate your field's data.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
}
},
"dependencies": {
"@hcaptcha/react-hcaptcha": "^0.3.6",
"file-saver": "^2.0.5",
"react-google-recaptcha-v3": "^1.8.0",
"volto-subblocks": "collective/volto-subblocks#v1.0.1"
Expand Down
194 changes: 102 additions & 92 deletions src/components/FormView.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useCallback, useState, useEffect } from 'react';
import { useIntl, defineMessages } from 'react-intl';
import HCaptcha from '@hcaptcha/react-hcaptcha';
import { GoogleReCaptcha } from 'react-google-recaptcha-v3';
import {
Segment,
Expand Down Expand Up @@ -46,19 +47,20 @@ const FormView = ({
}) => {
const intl = useIntl();

const [loadedRecaptcha, setLoadedRecaptcha] = useState(null);
const [loadedCaptcha, setLoadedCaptcha] = useState(null);
let validToken = '';
const onVerifyCaptcha = useCallback(
(token) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
console.log(token);
validToken = token;
},
[validToken],
);

useEffect(() => {
setLoadedRecaptcha(true);
}, [loadedRecaptcha]);
setLoadedCaptcha(true);
}, [loadedCaptcha]);

const isValidField = (field) => {
return formErrors?.indexOf(field) < 0;
Expand Down Expand Up @@ -93,99 +95,107 @@ const FormView = ({
</Button>
</Message>
) : (
<Form
loading={formState.loading}
onSubmit={onSubmit}
autoComplete="off"
method="post"
>
<Grid columns={1} padded="vertically">
{data.static_fields?.map((field) => (
<Grid.Row key={field.field_id} className="static-field">
<Grid.Column>
<Field
{...field}
field_type={field.field_type || 'text'}
name={field.label}
value={field.value}
onChange={() => {}}
disabled
valid
formHasErrors={formErrors.length > 0}
/>
</Grid.Column>
</Grid.Row>
))}
{data.subblocks?.map((subblock, index) => {
let name = getFieldName(subblock.label);
return (
<Grid.Row key={'row' + index}>
<Grid.Column>
<Field
{...subblock}
name={name}
onChange={(field, value) =>
onChangeFormData(
subblock.id,
field,
value,
subblock.label,
)
}
value={formData[name]?.value}
valid={isValidField(name)}
/>
</Grid.Column>
</Grid.Row>
);
})}
<Form
loading={formState.loading}
onSubmit={onSubmit}
autoComplete="off"
method="post"
>
<Grid columns={1} padded="vertically">
{data.static_fields?.map((field) => (
<Grid.Row key={field.field_id} className="static-field">
<Grid.Column>
<Field
{...field}
field_type={field.field_type || 'text'}
name={field.label}
value={field.value}
onChange={() => { }}
disabled
valid
formHasErrors={formErrors.length > 0}
/>
</Grid.Column>
</Grid.Row>
))}
{data.subblocks?.map((subblock, index) => {
let name = getFieldName(subblock.label);
return (
<Grid.Row key={'row' + index}>
<Grid.Column>
<Field
{...subblock}
name={name}
onChange={(field, value) =>
onChangeFormData(
subblock.id,
field,
value,
subblock.label,
)
}
value={formData[name]?.value}
valid={isValidField(name)}
/>
</Grid.Column>
</Grid.Row>
);
})}

{process.env.RAZZLE_RECAPTCHA_KEY && (
<Grid.Row>
<Grid.Column>
<GoogleReCaptcha onVerify={onVerifyCaptcha} />
</Grid.Column>
</Grid.Row>
)}
{process.env.RAZZLE_RECAPTCHA_KEY && (
<Grid.Row>
<Grid.Column>
<GoogleReCaptcha onVerify={onVerifyCaptcha} />
</Grid.Column>
</Grid.Row>
)}

{formErrors.length > 0 && (
<Message error role="alert">
<Message.Header as="h4">
{intl.formatMessage(messages.error)}
</Message.Header>
<p>{intl.formatMessage(messages.empty_values)}</p>
</Message>
)}
{process.env.RAZZLE_HCAPTCHA_KEY && (
<Grid.Row>
<Grid.Column>
<HCaptcha sitekey={process.env.RAZZLE_HCAPTCHA_KEY} onVerify={onVerifyCaptcha} size="invisible" />
</Grid.Column>
</Grid.Row>
)}

<Grid.Row centered style={{ paddingTop: '3rem' }}>
<Grid.Column textAlign="center">
<Button
primary
type="submit"
disabled={
(!loadedRecaptcha &&
process.env.RAZZLE_RECAPTCHA_KEY) ||
formState.loading
}
>
{data.submit_label ||
intl.formatMessage(messages.default_submit_label)}
{formErrors.length > 0 && (
<Message error role="alert">
<Message.Header as="h4">
{intl.formatMessage(messages.error)}
</Message.Header>
<p>{intl.formatMessage(messages.empty_values)}</p>
</Message>
)}

{formState.loading && (
<Progress
role="progressbar"
percent={100}
active
success={false}
color="grey"
/>
)}
</Button>
</Grid.Column>
</Grid.Row>
</Grid>
</Form>
)}
<Grid.Row centered style={{ paddingTop: '3rem' }}>
<Grid.Column textAlign="center">
<Button
primary
type="submit"
disabled={
(!loadedCaptcha &&
(process.env.RAZZLE_RECAPTCHA_KEY || process.env.RAZZLE_HCAPTCHA_KEY)) ||
formState.loading
}
>
{data.submit_label ||
intl.formatMessage(messages.default_submit_label)}

{formState.loading && (
<Progress
role="progressbar"
percent={100}
active
success={false}
color="grey"
/>
)}
</Button>
</Grid.Column>
</Grid.Row>
</Grid>
</Form>
)}
</Segment>
</div>
</div>
Expand Down

0 comments on commit 3ae4599

Please sign in to comment.