design-comuni-plone-theme 11.21.1 → 11.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.yarn/install-state.gz +0 -0
- package/CHANGELOG.md +30 -0
- package/RELEASE.md +18 -0
- package/locales/de/LC_MESSAGES/volto.po +40 -5
- package/locales/en/LC_MESSAGES/volto.po +40 -5
- package/locales/es/LC_MESSAGES/volto.po +40 -5
- package/locales/fr/LC_MESSAGES/volto.po +40 -5
- package/locales/it/LC_MESSAGES/volto.po +40 -5
- package/locales/volto.pot +41 -6
- package/package.json +1 -1
- package/publiccode.yml +2 -2
- package/src/components/ItaliaTheme/AppExtras/GenericAppExtras.jsx +1 -2
- package/src/components/ItaliaTheme/Blocks/EventSearch/DefaultFilters.js +1 -11
- package/src/components/ItaliaTheme/CustomerSatisfaction/FeedbackForm.jsx +3 -18
- package/src/components/ItaliaTheme/LoginAgid/LoginAgid.jsx +2 -5
- package/src/components/ItaliaTheme/Search/Search.jsx +2 -3
- package/src/components/ItaliaTheme/Unauthorized/Unauthorized.jsx +2 -5
- package/src/components/ItaliaTheme/View/PersonaView/PersonaDocumenti.jsx +50 -0
- package/src/components/ItaliaTheme/View/UOView/UOMetadati.jsx +12 -0
- package/src/components/ItaliaTheme/View/UOView/UOView.jsx +3 -0
- package/src/components/ItaliaTheme/View/index.js +1 -0
- package/src/config/italiaConfig.js +1 -0
- package/src/customizations/volto/actions/vocabularies/vocabularies.js +2 -2
- package/src/customizations/volto/components/manage/Blocks/Search/SearchBlockView.jsx +35 -9
- package/src/customizations/volto/components/manage/Blocks/Search/components/SearchDetails.jsx +2 -3
- package/src/customizations/volto/components/manage/Blocks/Search/hocs/withSearch.jsx +6 -4
- package/src/customizations/volto/components/theme/Footer/Footer.jsx +7 -13
- package/src/customizations/volto/helpers/BodyClass/BodyClass.jsx +78 -0
- package/src/customizations/volto-form-block/components/FormResult.jsx +39 -9
- package/src/customizations/volto-form-block/components/Sidebar.jsx +325 -0
- package/src/customizations/volto-form-block/components/View.jsx +390 -0
- package/src/customizations/volto-form-block/fieldSchema.js +174 -0
- package/src/customizations/volto-form-block/formSchema.js +237 -0
- package/src/overrideTranslations.jsx +26 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
// CUSTOMIZATION:
|
|
2
|
+
// - 112-113 reset subscription limit to default when set_limit is not active
|
|
3
|
+
|
|
4
|
+
import React, { useState, useEffect } from 'react';
|
|
5
|
+
import PropTypes from 'prop-types';
|
|
6
|
+
import { useSelector, useDispatch } from 'react-redux';
|
|
7
|
+
import {
|
|
8
|
+
Segment,
|
|
9
|
+
Accordion,
|
|
10
|
+
Form,
|
|
11
|
+
Button,
|
|
12
|
+
Grid,
|
|
13
|
+
Confirm,
|
|
14
|
+
Dimmer,
|
|
15
|
+
Loader,
|
|
16
|
+
} from 'semantic-ui-react';
|
|
17
|
+
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
|
18
|
+
|
|
19
|
+
import { Icon } from '@plone/volto/components';
|
|
20
|
+
|
|
21
|
+
import upSVG from '@plone/volto/icons/up-key.svg';
|
|
22
|
+
import downSVG from '@plone/volto/icons/down-key.svg';
|
|
23
|
+
import downloadSVG from '@plone/volto/icons/download.svg';
|
|
24
|
+
import deleteSVG from '@plone/volto/icons/delete.svg';
|
|
25
|
+
|
|
26
|
+
import warningSVG from '@plone/volto/icons/warning.svg';
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
getFormData,
|
|
30
|
+
exportCsvFormData,
|
|
31
|
+
clearFormData,
|
|
32
|
+
} from 'volto-form-block/actions';
|
|
33
|
+
|
|
34
|
+
import { BlockDataForm } from '@plone/volto/components';
|
|
35
|
+
import { flattenToAppURL } from '@plone/volto/helpers';
|
|
36
|
+
import { getFieldName } from 'volto-form-block/components/utils';
|
|
37
|
+
|
|
38
|
+
import 'volto-form-block/components/Sidebar.css';
|
|
39
|
+
import config from '@plone/volto/registry';
|
|
40
|
+
|
|
41
|
+
const messages = defineMessages({
|
|
42
|
+
exportCsv: {
|
|
43
|
+
id: 'form_edit_exportCsv',
|
|
44
|
+
defaultMessage: 'Export data',
|
|
45
|
+
},
|
|
46
|
+
clearData: {
|
|
47
|
+
id: 'form_clear_data',
|
|
48
|
+
defaultMessage: 'Clear data',
|
|
49
|
+
},
|
|
50
|
+
formDataCount: {
|
|
51
|
+
id: 'form_formDataCount',
|
|
52
|
+
defaultMessage: '{formDataCount} item(s) stored',
|
|
53
|
+
},
|
|
54
|
+
confirmClearData: {
|
|
55
|
+
id: 'form_confirmClearData',
|
|
56
|
+
defaultMessage: 'Are you sure you want to delete all saved items?',
|
|
57
|
+
},
|
|
58
|
+
cancel: {
|
|
59
|
+
id: 'Cancel',
|
|
60
|
+
defaultMessage: 'Cancel',
|
|
61
|
+
},
|
|
62
|
+
fieldId: {
|
|
63
|
+
id: 'fieldId',
|
|
64
|
+
defaultMessage: 'Field ID',
|
|
65
|
+
},
|
|
66
|
+
remove_data_cron_info: {
|
|
67
|
+
id: 'remove_data_cron_info',
|
|
68
|
+
defaultMessage:
|
|
69
|
+
'To automate the removal of records that have exceeded the maximum number of days indicated in configuration, a cron must be set up on the server as indicated in the product documentation.',
|
|
70
|
+
},
|
|
71
|
+
remove_data_warning: {
|
|
72
|
+
id: 'remove_data_warning',
|
|
73
|
+
defaultMessage:
|
|
74
|
+
'There are {record} record that have exceeded the maximum number of days.',
|
|
75
|
+
},
|
|
76
|
+
remove_data_button: {
|
|
77
|
+
id: 'remove_data_button',
|
|
78
|
+
defaultMessage: 'remove expired data',
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const Sidebar = ({
|
|
83
|
+
data,
|
|
84
|
+
properties,
|
|
85
|
+
block,
|
|
86
|
+
onChangeBlock,
|
|
87
|
+
onChangeSubBlock,
|
|
88
|
+
selected = 0,
|
|
89
|
+
setSelected,
|
|
90
|
+
}) => {
|
|
91
|
+
const intl = useIntl();
|
|
92
|
+
const dispatch = useDispatch();
|
|
93
|
+
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
94
|
+
|
|
95
|
+
const formData = useSelector((state) => state.formData);
|
|
96
|
+
const clearFormDataState = useSelector(
|
|
97
|
+
(state) => state.clearFormData?.loaded,
|
|
98
|
+
);
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (properties?.['@id'])
|
|
101
|
+
dispatch(
|
|
102
|
+
getFormData({
|
|
103
|
+
path: flattenToAppURL(properties['@id']),
|
|
104
|
+
block_id: block,
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
108
|
+
}, [clearFormDataState]);
|
|
109
|
+
|
|
110
|
+
if (data.send_email === undefined) data.send_email = true;
|
|
111
|
+
|
|
112
|
+
// reset subscription limit to default when set_limit is not active
|
|
113
|
+
if (data.set_limit === false) data.limit = -1;
|
|
114
|
+
|
|
115
|
+
data.subblocks &&
|
|
116
|
+
data.subblocks.forEach((subblock) => {
|
|
117
|
+
subblock.field_id = subblock.id;
|
|
118
|
+
});
|
|
119
|
+
var FormSchema = config.blocks.blocksConfig.form.formSchema;
|
|
120
|
+
var FieldSchema = config.blocks.blocksConfig.form.fieldSchema;
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<Form>
|
|
124
|
+
<Segment.Group raised>
|
|
125
|
+
<header className="header pulled">
|
|
126
|
+
<h2>
|
|
127
|
+
<FormattedMessage id="Form" defaultMessage="Form" />
|
|
128
|
+
</h2>
|
|
129
|
+
</header>
|
|
130
|
+
<Segment>
|
|
131
|
+
<BlockDataForm
|
|
132
|
+
schema={FormSchema(data)}
|
|
133
|
+
onChangeField={(id, value) => {
|
|
134
|
+
onChangeBlock(block, {
|
|
135
|
+
...data,
|
|
136
|
+
[id]: value,
|
|
137
|
+
});
|
|
138
|
+
}}
|
|
139
|
+
formData={data}
|
|
140
|
+
/>
|
|
141
|
+
{properties?.['@components']?.form_data && (
|
|
142
|
+
<Form.Field inline>
|
|
143
|
+
<Grid>
|
|
144
|
+
<Grid.Row stretched centered style={{ padding: '1rem 0' }}>
|
|
145
|
+
<Dimmer active={formData?.loading}>
|
|
146
|
+
<Loader size="tiny" />
|
|
147
|
+
</Dimmer>
|
|
148
|
+
<p>
|
|
149
|
+
{intl.formatMessage(messages.formDataCount, {
|
|
150
|
+
formDataCount: formData?.result?.items_total ?? 0,
|
|
151
|
+
})}
|
|
152
|
+
</p>
|
|
153
|
+
</Grid.Row>
|
|
154
|
+
<Grid.Row
|
|
155
|
+
stretched
|
|
156
|
+
centered
|
|
157
|
+
columns={2}
|
|
158
|
+
style={{ marginBottom: '0.5rem' }}
|
|
159
|
+
>
|
|
160
|
+
<Grid.Column>
|
|
161
|
+
<Button
|
|
162
|
+
compact
|
|
163
|
+
onClick={() =>
|
|
164
|
+
dispatch(
|
|
165
|
+
exportCsvFormData(
|
|
166
|
+
flattenToAppURL(properties['@id']),
|
|
167
|
+
`export-${properties.id ?? 'form'}.csv`,
|
|
168
|
+
block,
|
|
169
|
+
),
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
size="tiny"
|
|
173
|
+
style={{ display: 'flex', alignItems: 'center' }}
|
|
174
|
+
>
|
|
175
|
+
<Icon name={downloadSVG} size="1.5rem" />{' '}
|
|
176
|
+
{intl.formatMessage(messages.exportCsv)}
|
|
177
|
+
</Button>
|
|
178
|
+
</Grid.Column>
|
|
179
|
+
<Grid.Column>
|
|
180
|
+
<Button
|
|
181
|
+
compact
|
|
182
|
+
onClick={() => setConfirmOpen(true)}
|
|
183
|
+
size="tiny"
|
|
184
|
+
style={{ display: 'flex', alignItems: 'center' }}
|
|
185
|
+
>
|
|
186
|
+
<Icon name={deleteSVG} size="1.5rem" />{' '}
|
|
187
|
+
{intl.formatMessage(messages.clearData)}
|
|
188
|
+
</Button>
|
|
189
|
+
<Confirm
|
|
190
|
+
open={confirmOpen}
|
|
191
|
+
content={intl.formatMessage(messages.confirmClearData)}
|
|
192
|
+
cancelButton={intl.formatMessage(messages.cancel)}
|
|
193
|
+
onCancel={() => setConfirmOpen(false)}
|
|
194
|
+
onConfirm={() => {
|
|
195
|
+
dispatch(
|
|
196
|
+
clearFormData({
|
|
197
|
+
path: flattenToAppURL(properties['@id']),
|
|
198
|
+
block_id: block,
|
|
199
|
+
}),
|
|
200
|
+
);
|
|
201
|
+
setConfirmOpen(false);
|
|
202
|
+
}}
|
|
203
|
+
/>
|
|
204
|
+
</Grid.Column>
|
|
205
|
+
</Grid.Row>
|
|
206
|
+
{data.remove_data_after_days > 0 && (
|
|
207
|
+
<Grid.Row>
|
|
208
|
+
<div class="ui message info tiny">
|
|
209
|
+
{formData.loaded &&
|
|
210
|
+
formData.result?.expired_total > 0 && (
|
|
211
|
+
<>
|
|
212
|
+
<p>
|
|
213
|
+
<Icon name={warningSVG} size="18px" />
|
|
214
|
+
{intl.formatMessage(
|
|
215
|
+
messages.remove_data_warning,
|
|
216
|
+
{
|
|
217
|
+
record: formData.result.expired_total,
|
|
218
|
+
},
|
|
219
|
+
)}
|
|
220
|
+
</p>
|
|
221
|
+
<p>
|
|
222
|
+
<Button
|
|
223
|
+
onClick={() =>
|
|
224
|
+
dispatch(
|
|
225
|
+
clearFormData({
|
|
226
|
+
path: flattenToAppURL(properties['@id']),
|
|
227
|
+
expired: true,
|
|
228
|
+
block_id: block,
|
|
229
|
+
}),
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
size="tiny"
|
|
233
|
+
compact
|
|
234
|
+
style={{
|
|
235
|
+
display: 'flex',
|
|
236
|
+
alignItems: 'center',
|
|
237
|
+
}}
|
|
238
|
+
>
|
|
239
|
+
<Icon name={deleteSVG} size="1.5rem" />{' '}
|
|
240
|
+
{intl.formatMessage(
|
|
241
|
+
messages.remove_data_button,
|
|
242
|
+
)}
|
|
243
|
+
</Button>
|
|
244
|
+
</p>
|
|
245
|
+
</>
|
|
246
|
+
)}
|
|
247
|
+
<p>
|
|
248
|
+
{intl.formatMessage(messages.remove_data_cron_info)}
|
|
249
|
+
</p>
|
|
250
|
+
</div>
|
|
251
|
+
</Grid.Row>
|
|
252
|
+
)}
|
|
253
|
+
</Grid>
|
|
254
|
+
</Form.Field>
|
|
255
|
+
)}
|
|
256
|
+
</Segment>
|
|
257
|
+
<Accordion fluid styled className="form">
|
|
258
|
+
{data.subblocks &&
|
|
259
|
+
data.subblocks.map((subblock, index) => {
|
|
260
|
+
return (
|
|
261
|
+
<div key={'subblock' + index}>
|
|
262
|
+
<Accordion.Title
|
|
263
|
+
active={selected === index}
|
|
264
|
+
index={index}
|
|
265
|
+
onClick={() =>
|
|
266
|
+
setSelected(selected === index ? null : index)
|
|
267
|
+
}
|
|
268
|
+
>
|
|
269
|
+
{subblock.label ?? subblock.field_name}
|
|
270
|
+
{selected === index ? (
|
|
271
|
+
<Icon name={upSVG} size="20px" />
|
|
272
|
+
) : (
|
|
273
|
+
<Icon name={downSVG} size="20px" />
|
|
274
|
+
)}
|
|
275
|
+
</Accordion.Title>
|
|
276
|
+
<Accordion.Content active={selected === index}>
|
|
277
|
+
{/* Field ID info */}
|
|
278
|
+
{(subblock.field_type === 'text' ||
|
|
279
|
+
subblock.field_type === 'from' ||
|
|
280
|
+
subblock.field_type === 'textarea' ||
|
|
281
|
+
subblock.field_type === 'date' ||
|
|
282
|
+
subblock.field_type === 'single_choice' ||
|
|
283
|
+
subblock.field_type === 'multiple_choice' ||
|
|
284
|
+
subblock.field_type === 'select' ||
|
|
285
|
+
subblock.field_type === 'checkbox' ||
|
|
286
|
+
subblock.field_type === 'attachment') && (
|
|
287
|
+
<Segment tertiary>
|
|
288
|
+
{intl.formatMessage(messages.fieldId)}:{' '}
|
|
289
|
+
<strong>
|
|
290
|
+
{getFieldName(subblock.label, subblock.field_id)}
|
|
291
|
+
</strong>
|
|
292
|
+
</Segment>
|
|
293
|
+
)}
|
|
294
|
+
<BlockDataForm
|
|
295
|
+
schema={FieldSchema(subblock)}
|
|
296
|
+
onChangeField={(name, value) => {
|
|
297
|
+
var update_values = {};
|
|
298
|
+
|
|
299
|
+
onChangeSubBlock(index, {
|
|
300
|
+
...subblock,
|
|
301
|
+
[name]: value,
|
|
302
|
+
...update_values,
|
|
303
|
+
});
|
|
304
|
+
}}
|
|
305
|
+
formData={subblock}
|
|
306
|
+
/>
|
|
307
|
+
</Accordion.Content>
|
|
308
|
+
</div>
|
|
309
|
+
);
|
|
310
|
+
})}
|
|
311
|
+
</Accordion>
|
|
312
|
+
</Segment.Group>
|
|
313
|
+
</Form>
|
|
314
|
+
);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
Sidebar.propTypes = {
|
|
318
|
+
data: PropTypes.objectOf(PropTypes.any),
|
|
319
|
+
block: PropTypes.string,
|
|
320
|
+
onChangeBlock: PropTypes.func,
|
|
321
|
+
selected: PropTypes.any,
|
|
322
|
+
setSelected: PropTypes.func,
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
export default Sidebar;
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
// CUSTOMIZATION:
|
|
2
|
+
// - added warning state to form
|
|
3
|
+
|
|
4
|
+
import React, { useState, useEffect, useReducer, useRef } from 'react';
|
|
5
|
+
import { useSelector, useDispatch } from 'react-redux';
|
|
6
|
+
import PropTypes from 'prop-types';
|
|
7
|
+
import { useIntl, defineMessages } from 'react-intl';
|
|
8
|
+
import { submitForm } from 'volto-form-block/actions';
|
|
9
|
+
import { getFieldName } from 'volto-form-block/components/utils';
|
|
10
|
+
import FormView from 'volto-form-block/components/FormView';
|
|
11
|
+
import { formatDate } from '@plone/volto/helpers/Utils/Date';
|
|
12
|
+
import config from '@plone/volto/registry';
|
|
13
|
+
import { Captcha } from 'volto-form-block/components/Widget';
|
|
14
|
+
import { isValidEmail } from 'volto-form-block/helpers/validators';
|
|
15
|
+
import ValidateConfigForm from 'volto-form-block/components/ValidateConfigForm';
|
|
16
|
+
import { OTP_FIELDNAME_EXTENDER } from 'volto-form-block/components/Widget';
|
|
17
|
+
|
|
18
|
+
const messages = defineMessages({
|
|
19
|
+
formSubmitted: {
|
|
20
|
+
id: 'formSubmitted',
|
|
21
|
+
defaultMessage: 'Form successfully submitted',
|
|
22
|
+
},
|
|
23
|
+
defaultInvalidFieldMessage: {
|
|
24
|
+
id: 'formblock_defaultInvalidFieldMessage',
|
|
25
|
+
defaultMessage: 'Invalid field value',
|
|
26
|
+
},
|
|
27
|
+
requiredFieldMessage: {
|
|
28
|
+
id: 'formblock_requiredFieldMessage',
|
|
29
|
+
defaultMessage: 'Fill-in this field',
|
|
30
|
+
},
|
|
31
|
+
invalidEmailMessage: {
|
|
32
|
+
id: 'formblock_invalidEmailMessage',
|
|
33
|
+
defaultMessage: 'The email is incorrect',
|
|
34
|
+
},
|
|
35
|
+
insertOtp: {
|
|
36
|
+
id: 'formblock_insertOtp_error',
|
|
37
|
+
defaultMessage: 'Please, insert the OTP code recived via email.',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const initialState = {
|
|
42
|
+
loading: false,
|
|
43
|
+
error: null,
|
|
44
|
+
result: null,
|
|
45
|
+
warning: null,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const FORM_STATES = {
|
|
49
|
+
normal: 'normal',
|
|
50
|
+
loading: 'loading',
|
|
51
|
+
error: 'error',
|
|
52
|
+
success: 'success',
|
|
53
|
+
warning: 'warning',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const formStateReducer = (state, action) => {
|
|
57
|
+
switch (action.type) {
|
|
58
|
+
case FORM_STATES.normal:
|
|
59
|
+
return initialState;
|
|
60
|
+
|
|
61
|
+
case FORM_STATES.loading:
|
|
62
|
+
return { loading: true, error: null, result: null, warning: null };
|
|
63
|
+
|
|
64
|
+
case FORM_STATES.error:
|
|
65
|
+
return {
|
|
66
|
+
loading: false,
|
|
67
|
+
error: action.error,
|
|
68
|
+
result: null,
|
|
69
|
+
warning: null,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
case FORM_STATES.warning:
|
|
73
|
+
return {
|
|
74
|
+
loading: false,
|
|
75
|
+
error: null,
|
|
76
|
+
result: action.result,
|
|
77
|
+
warning: true,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
case FORM_STATES.success:
|
|
81
|
+
return {
|
|
82
|
+
loading: false,
|
|
83
|
+
error: null,
|
|
84
|
+
result: action.result,
|
|
85
|
+
warning: null,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
default:
|
|
89
|
+
return initialState;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const getInitialData = (data) => {
|
|
94
|
+
const { static_fields = [], subblocks = [] } = data;
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
...subblocks.reduce(
|
|
98
|
+
(acc, field) =>
|
|
99
|
+
field.field_type === 'hidden'
|
|
100
|
+
? {
|
|
101
|
+
...acc,
|
|
102
|
+
[getFieldName(field.label, field.id)]: {
|
|
103
|
+
...field,
|
|
104
|
+
...(data[field.id] && { custom_field_id: data[field.id] }),
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
: acc,
|
|
108
|
+
{},
|
|
109
|
+
),
|
|
110
|
+
...static_fields.reduce(
|
|
111
|
+
(acc, field) => ({
|
|
112
|
+
...acc,
|
|
113
|
+
[getFieldName(field.label, field.id)]: field,
|
|
114
|
+
}),
|
|
115
|
+
{},
|
|
116
|
+
),
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Form view
|
|
122
|
+
* @class View
|
|
123
|
+
*/
|
|
124
|
+
const View = ({ data, id, path }) => {
|
|
125
|
+
const intl = useIntl();
|
|
126
|
+
const dispatch = useDispatch();
|
|
127
|
+
|
|
128
|
+
const [formData, setFormData] = useReducer((state, action) => {
|
|
129
|
+
if (action.reset) {
|
|
130
|
+
return getInitialData(data);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
...state,
|
|
135
|
+
[action.field]: action.value,
|
|
136
|
+
};
|
|
137
|
+
}, getInitialData(data));
|
|
138
|
+
|
|
139
|
+
const [formState, setFormState] = useReducer(formStateReducer, initialState);
|
|
140
|
+
const [formErrors, setFormErrors] = useState([]);
|
|
141
|
+
|
|
142
|
+
const submitResults = useSelector(
|
|
143
|
+
(state) => state.submitForm?.subrequests?.[id],
|
|
144
|
+
);
|
|
145
|
+
const captchaToken = useRef();
|
|
146
|
+
|
|
147
|
+
const onChangeFormData = (field_id, field, value, extras) => {
|
|
148
|
+
setFormData({ field, value: { field_id, value, ...extras } });
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
if (formErrors.length > 0) {
|
|
153
|
+
isValidForm();
|
|
154
|
+
}
|
|
155
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
156
|
+
}, [formData]);
|
|
157
|
+
|
|
158
|
+
const isValidForm = () => {
|
|
159
|
+
const v = [];
|
|
160
|
+
data.subblocks.forEach((subblock, index) => {
|
|
161
|
+
const name = getFieldName(subblock.label, subblock.id);
|
|
162
|
+
const fieldType = subblock.field_type;
|
|
163
|
+
const isBCC = subblock.use_as_bcc;
|
|
164
|
+
const additionalField =
|
|
165
|
+
config.blocks.blocksConfig.form.additionalFields?.filter(
|
|
166
|
+
(f) => f.id === fieldType && f.isValid !== undefined,
|
|
167
|
+
)?.[0] ?? null;
|
|
168
|
+
|
|
169
|
+
if (
|
|
170
|
+
subblock.required &&
|
|
171
|
+
additionalField &&
|
|
172
|
+
!additionalField?.isValid(formData, name)
|
|
173
|
+
) {
|
|
174
|
+
const validation = additionalField?.isValid(formData, name);
|
|
175
|
+
if (typeof validation === 'boolean') {
|
|
176
|
+
//for retro-compatibility with previous formErrors structure
|
|
177
|
+
v.push({
|
|
178
|
+
field: name,
|
|
179
|
+
message: intl.formatMessage(messages.defaultInvalidFieldMessage),
|
|
180
|
+
});
|
|
181
|
+
} else if (typeof validation === 'object') {
|
|
182
|
+
v.push(validation);
|
|
183
|
+
}
|
|
184
|
+
} else if (
|
|
185
|
+
subblock.required &&
|
|
186
|
+
fieldType === 'checkbox' &&
|
|
187
|
+
!formData[name]?.value
|
|
188
|
+
) {
|
|
189
|
+
v.push({
|
|
190
|
+
field: name,
|
|
191
|
+
message: intl.formatMessage(messages.requiredFieldMessage),
|
|
192
|
+
});
|
|
193
|
+
} else if (
|
|
194
|
+
subblock.required &&
|
|
195
|
+
(!formData[name] ||
|
|
196
|
+
formData[name]?.value?.length === 0 ||
|
|
197
|
+
JSON.stringify(formData[name]?.value ?? {}) === '{}')
|
|
198
|
+
) {
|
|
199
|
+
v.push({
|
|
200
|
+
field: name,
|
|
201
|
+
message: intl.formatMessage(messages.requiredFieldMessage),
|
|
202
|
+
});
|
|
203
|
+
} else if (
|
|
204
|
+
(fieldType === 'from' || fieldType === 'email') &&
|
|
205
|
+
formData[name]?.value
|
|
206
|
+
) {
|
|
207
|
+
if (!isValidEmail(formData[name].value)) {
|
|
208
|
+
v.push({
|
|
209
|
+
field: name,
|
|
210
|
+
message: intl.formatMessage(messages.invalidEmailMessage),
|
|
211
|
+
});
|
|
212
|
+
} else if (isBCC && !formData[name].otp) {
|
|
213
|
+
v.push({
|
|
214
|
+
field: name + OTP_FIELDNAME_EXTENDER,
|
|
215
|
+
message: intl.formatMessage(messages.insertOtp),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (data.captcha && !captchaToken.current) {
|
|
222
|
+
v.push({
|
|
223
|
+
field: 'captcha',
|
|
224
|
+
message: intl.formatMessage(messages.requiredFieldMessage),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
setFormErrors(v);
|
|
229
|
+
return v.length === 0;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const submit = (e) => {
|
|
233
|
+
e.preventDefault();
|
|
234
|
+
captcha
|
|
235
|
+
.verify()
|
|
236
|
+
.then(() => {
|
|
237
|
+
if (isValidForm()) {
|
|
238
|
+
let attachments = {};
|
|
239
|
+
let captcha = {
|
|
240
|
+
provider: data.captcha,
|
|
241
|
+
token: captchaToken.current,
|
|
242
|
+
};
|
|
243
|
+
if (data.captcha === 'honeypot') {
|
|
244
|
+
captcha.value = formData[data.captcha_props.id]?.value ?? '';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let formattedFormData = { ...formData };
|
|
248
|
+
data.subblocks.forEach((subblock) => {
|
|
249
|
+
let name = getFieldName(subblock.label, subblock.id);
|
|
250
|
+
if (formattedFormData[name]?.value) {
|
|
251
|
+
formattedFormData[name].field_id = subblock.field_id;
|
|
252
|
+
const isAttachment =
|
|
253
|
+
config.blocks.blocksConfig.form.attachment_fields.includes(
|
|
254
|
+
subblock.field_type,
|
|
255
|
+
);
|
|
256
|
+
const isDate = subblock.field_type === 'date';
|
|
257
|
+
|
|
258
|
+
if (isAttachment) {
|
|
259
|
+
attachments[name] = formattedFormData[name].value;
|
|
260
|
+
delete formattedFormData[name];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (isDate) {
|
|
264
|
+
formattedFormData[name].value = formatDate({
|
|
265
|
+
date: formattedFormData[name].value,
|
|
266
|
+
format: 'DD-MM-YYYY',
|
|
267
|
+
locale: intl.locale,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
dispatch(
|
|
273
|
+
submitForm(
|
|
274
|
+
path,
|
|
275
|
+
id,
|
|
276
|
+
Object.keys(formattedFormData).map((name) => ({
|
|
277
|
+
...formattedFormData[name],
|
|
278
|
+
})),
|
|
279
|
+
attachments,
|
|
280
|
+
captcha,
|
|
281
|
+
),
|
|
282
|
+
);
|
|
283
|
+
setFormState({ type: FORM_STATES.loading });
|
|
284
|
+
} else {
|
|
285
|
+
setFormState({ type: FORM_STATES.error });
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
.catch(() => {
|
|
289
|
+
setFormState({ type: FORM_STATES.error });
|
|
290
|
+
});
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const resetFormState = () => {
|
|
294
|
+
setFormData({ reset: true });
|
|
295
|
+
setFormState({ type: FORM_STATES.normal });
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const resetFormOnError = () => {
|
|
299
|
+
setFormState({ type: FORM_STATES.normal });
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const getErrorMessage = (field) => {
|
|
303
|
+
const e = formErrors?.filter((e) => e.field === field);
|
|
304
|
+
return e.length > 0 ? e[0].message : null;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const captcha = new Captcha({
|
|
308
|
+
captchaToken,
|
|
309
|
+
captcha: data.captcha,
|
|
310
|
+
captcha_props: data.captcha_props,
|
|
311
|
+
errorMessage: getErrorMessage('captcha'),
|
|
312
|
+
onChangeFormData,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
const formid = `form-${id}`;
|
|
316
|
+
|
|
317
|
+
useEffect(() => {
|
|
318
|
+
if (submitResults?.loaded) {
|
|
319
|
+
if (submitResults?.result?.data?.waiting_list) {
|
|
320
|
+
setFormState({
|
|
321
|
+
type: FORM_STATES.warning,
|
|
322
|
+
result: {
|
|
323
|
+
...submitResults.result,
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
} else {
|
|
327
|
+
setFormState({
|
|
328
|
+
type: FORM_STATES.success,
|
|
329
|
+
result: {
|
|
330
|
+
message: intl.formatMessage(messages.formSubmitted),
|
|
331
|
+
...submitResults.result,
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
captcha.reset();
|
|
336
|
+
const formItem = document.getElementById(formid);
|
|
337
|
+
if (formItem !== null) {
|
|
338
|
+
const formItemPosition = formItem.getBoundingClientRect();
|
|
339
|
+
formItemPosition !== null &&
|
|
340
|
+
window.scrollTo({
|
|
341
|
+
top: formItemPosition.x,
|
|
342
|
+
left: formItemPosition.y,
|
|
343
|
+
behavior: 'smooth',
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
} else if (submitResults?.error) {
|
|
347
|
+
let errorDescription = `${
|
|
348
|
+
JSON.parse(submitResults.error.response?.text ?? '{}')?.message
|
|
349
|
+
}`;
|
|
350
|
+
|
|
351
|
+
setFormState({ type: FORM_STATES.error, error: errorDescription });
|
|
352
|
+
}
|
|
353
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
354
|
+
}, [submitResults]);
|
|
355
|
+
|
|
356
|
+
useEffect(() => {
|
|
357
|
+
resetFormState();
|
|
358
|
+
}, []);
|
|
359
|
+
|
|
360
|
+
return (
|
|
361
|
+
<ValidateConfigForm data={data}>
|
|
362
|
+
<FormView
|
|
363
|
+
id={formid}
|
|
364
|
+
formState={formState}
|
|
365
|
+
formErrors={formErrors}
|
|
366
|
+
formData={formData}
|
|
367
|
+
captcha={captcha}
|
|
368
|
+
onChangeFormData={onChangeFormData}
|
|
369
|
+
data={data}
|
|
370
|
+
onSubmit={submit}
|
|
371
|
+
resetFormState={resetFormState}
|
|
372
|
+
resetFormOnError={resetFormOnError}
|
|
373
|
+
getErrorMessage={getErrorMessage}
|
|
374
|
+
path={path}
|
|
375
|
+
block_id={id}
|
|
376
|
+
/>
|
|
377
|
+
</ValidateConfigForm>
|
|
378
|
+
);
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Property types.
|
|
383
|
+
* @property {Object} propTypes Property types.
|
|
384
|
+
* @static
|
|
385
|
+
*/
|
|
386
|
+
View.propTypes = {
|
|
387
|
+
data: PropTypes.objectOf(PropTypes.any).isRequired,
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
export default View;
|