@eeacms/volto-eea-website-theme 0.5.2 → 0.5.3

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.
@@ -0,0 +1,775 @@
1
+ /**
2
+ * Form component.
3
+ * @module components/manage/Form/Form
4
+ */
5
+
6
+ import { BlocksForm, Field, Icon, Toast } from '@plone/volto/components';
7
+ import {
8
+ difference,
9
+ FormValidation,
10
+ getBlocksFieldname,
11
+ getBlocksLayoutFieldname,
12
+ messages,
13
+ } from '@plone/volto/helpers';
14
+ import aheadSVG from '@plone/volto/icons/ahead.svg';
15
+ import clearSVG from '@plone/volto/icons/clear.svg';
16
+ import {
17
+ findIndex,
18
+ isEmpty,
19
+ keys,
20
+ map,
21
+ mapValues,
22
+ pickBy,
23
+ without,
24
+ cloneDeep,
25
+ } from 'lodash';
26
+ import isBoolean from 'lodash/isBoolean';
27
+ import PropTypes from 'prop-types';
28
+ import React, { Component } from 'react';
29
+ import { injectIntl } from 'react-intl';
30
+ import { Portal } from 'react-portal';
31
+ import { connect } from 'react-redux';
32
+ import {
33
+ Button,
34
+ Container,
35
+ Form as UiForm,
36
+ Message,
37
+ Segment,
38
+ Tab,
39
+ } from 'semantic-ui-react';
40
+ import { v4 as uuid } from 'uuid';
41
+ import { toast } from 'react-toastify';
42
+ import { BlocksToolbar, UndoToolbar } from '@plone/volto/components';
43
+ import { setSidebarTab } from '@plone/volto/actions';
44
+ import { compose } from 'redux';
45
+ import config from '@plone/volto/registry';
46
+
47
+ /**
48
+ * Form container class.
49
+ * @class Form
50
+ * @extends Component
51
+ */
52
+ class Form extends Component {
53
+ /**
54
+ * Property types.
55
+ * @property {Object} propTypes Property types.
56
+ * @static
57
+ */
58
+ static propTypes = {
59
+ schema: PropTypes.shape({
60
+ fieldsets: PropTypes.arrayOf(
61
+ PropTypes.shape({
62
+ fields: PropTypes.arrayOf(PropTypes.string),
63
+ id: PropTypes.string,
64
+ title: PropTypes.string,
65
+ }),
66
+ ),
67
+ properties: PropTypes.objectOf(PropTypes.any),
68
+ definitions: PropTypes.objectOf(PropTypes.any),
69
+ required: PropTypes.arrayOf(PropTypes.string),
70
+ }),
71
+ formData: PropTypes.objectOf(PropTypes.any),
72
+ pathname: PropTypes.string,
73
+ onSubmit: PropTypes.func,
74
+ onCancel: PropTypes.func,
75
+ submitLabel: PropTypes.string,
76
+ resetAfterSubmit: PropTypes.bool,
77
+ isEditForm: PropTypes.bool,
78
+ isAdminForm: PropTypes.bool,
79
+ title: PropTypes.string,
80
+ error: PropTypes.shape({
81
+ message: PropTypes.string,
82
+ }),
83
+ loading: PropTypes.bool,
84
+ hideActions: PropTypes.bool,
85
+ description: PropTypes.string,
86
+ visual: PropTypes.bool,
87
+ blocks: PropTypes.arrayOf(PropTypes.object),
88
+ isFormSelected: PropTypes.bool,
89
+ onSelectForm: PropTypes.func,
90
+ editable: PropTypes.bool,
91
+ onChangeFormData: PropTypes.func,
92
+ requestError: PropTypes.string,
93
+ allowedBlocks: PropTypes.arrayOf(PropTypes.string),
94
+ showRestricted: PropTypes.bool,
95
+ };
96
+
97
+ /**
98
+ * Default properties.
99
+ * @property {Object} defaultProps Default properties.
100
+ * @static
101
+ */
102
+ static defaultProps = {
103
+ formData: null,
104
+ onSubmit: null,
105
+ onCancel: null,
106
+ submitLabel: null,
107
+ resetAfterSubmit: false,
108
+ isEditForm: false,
109
+ isAdminForm: false,
110
+ title: null,
111
+ description: null,
112
+ error: null,
113
+ loading: null,
114
+ hideActions: false,
115
+ visual: false,
116
+ blocks: [],
117
+ pathname: '',
118
+ schema: {},
119
+ isFormSelected: true,
120
+ onSelectForm: null,
121
+ editable: true,
122
+ requestError: null,
123
+ allowedBlocks: null,
124
+ };
125
+
126
+ /**
127
+ * Constructor
128
+ * @method constructor
129
+ * @param {Object} props Component properties
130
+ * @constructs Form
131
+ */
132
+ constructor(props) {
133
+ super(props);
134
+ const ids = {
135
+ title: uuid(),
136
+ text: uuid(),
137
+ };
138
+ let { formData } = props;
139
+ const blocksFieldname = getBlocksFieldname(formData);
140
+ const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);
141
+
142
+ if (!props.isEditForm) {
143
+ // It's a normal (add form), get defaults from schema
144
+ formData = {
145
+ ...mapValues(props.schema.properties, 'default'),
146
+ ...formData,
147
+ };
148
+ }
149
+ // defaults for block editor; should be moved to schema on server side
150
+ // Adding fallback in case the fields are empty, so we are sure that the edit form
151
+ // shows at least the default blocks
152
+ if (
153
+ formData.hasOwnProperty(blocksFieldname) &&
154
+ formData.hasOwnProperty(blocksLayoutFieldname)
155
+ ) {
156
+ if (
157
+ !formData[blocksLayoutFieldname] ||
158
+ isEmpty(formData[blocksLayoutFieldname].items)
159
+ ) {
160
+ formData[blocksLayoutFieldname] = {
161
+ items: [ids.title, ids.text],
162
+ };
163
+ }
164
+ if (!formData[blocksFieldname] || isEmpty(formData[blocksFieldname])) {
165
+ formData[blocksFieldname] = {
166
+ [ids.title]: {
167
+ '@type': 'title',
168
+ },
169
+ [ids.text]: {
170
+ '@type': config.settings.defaultBlockType,
171
+ },
172
+ };
173
+ }
174
+ }
175
+
176
+ let selectedBlock = null;
177
+ if (
178
+ formData.hasOwnProperty(blocksLayoutFieldname) &&
179
+ formData[blocksLayoutFieldname].items.length > 0
180
+ ) {
181
+ selectedBlock = formData[blocksLayoutFieldname].items[0];
182
+
183
+ if (config.blocks?.initialBlocksFocus?.[this.props.type]) {
184
+ //Default selected is not the first block, but the one from config.
185
+ Object.keys(formData[blocksFieldname]).forEach((b_key) => {
186
+ if (
187
+ formData[blocksFieldname][b_key]['@type'] ===
188
+ config.blocks?.initialBlocksFocus?.[this.props.type]
189
+ ) {
190
+ selectedBlock = b_key;
191
+ }
192
+ });
193
+ }
194
+ }
195
+ this.state = {
196
+ formData,
197
+ initialFormData: cloneDeep(formData),
198
+ errors: {},
199
+ selected: selectedBlock,
200
+ multiSelected: [],
201
+ isClient: false,
202
+ };
203
+ this.onChangeField = this.onChangeField.bind(this);
204
+ this.onSelectBlock = this.onSelectBlock.bind(this);
205
+ this.onSubmit = this.onSubmit.bind(this);
206
+ this.onCancel = this.onCancel.bind(this);
207
+ this.onTabChange = this.onTabChange.bind(this);
208
+ this.onBlurField = this.onBlurField.bind(this);
209
+ this.onClickInput = this.onClickInput.bind(this);
210
+ }
211
+
212
+ /**
213
+ * On updates caused by props change
214
+ * if errors from Backend come, these will be shown to their corresponding Fields
215
+ * also the first Tab to have any errors will be selected
216
+ * @param {Object} prevProps
217
+ */
218
+ async componentDidUpdate(prevProps, prevState) {
219
+ let { requestError } = this.props;
220
+ let errors = {};
221
+ let activeIndex = 0;
222
+
223
+ if (requestError && prevProps.requestError !== requestError) {
224
+ errors = FormValidation.giveServerErrorsToCorrespondingFields(
225
+ requestError,
226
+ );
227
+ activeIndex = FormValidation.showFirstTabWithErrors({
228
+ errors,
229
+ schema: this.props.schema,
230
+ });
231
+
232
+ this.setState({
233
+ errors,
234
+ activeIndex,
235
+ });
236
+ }
237
+
238
+ if (this.props.onChangeFormData) {
239
+ if (
240
+ // TODO: use fast-deep-equal
241
+ JSON.stringify(prevState?.formData) !==
242
+ JSON.stringify(this.state.formData)
243
+ ) {
244
+ this.props.onChangeFormData(this.state.formData);
245
+ }
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Tab selection is done only by setting activeIndex in state
251
+ */
252
+ onTabChange(e, { activeIndex }) {
253
+ this.setState({ activeIndex });
254
+ }
255
+
256
+ /**
257
+ * If user clicks on input, the form will be not considered pristine
258
+ * this will avoid onBlur effects without interraction with the form
259
+ * @param {Object} e event
260
+ */
261
+ onClickInput(e) {
262
+ this.setState({ isFormPristine: false });
263
+ }
264
+
265
+ /**
266
+ * Validate fields on blur
267
+ * @method onBlurField
268
+ * @param {string} id Id of the field
269
+ * @param {*} value Value of the field
270
+ * @returns {undefined}
271
+ */
272
+ onBlurField(id, value) {
273
+ if (!this.state.isFormPristine) {
274
+ const errors = FormValidation.validateFieldsPerFieldset({
275
+ schema: this.props.schema,
276
+ formData: this.state.formData,
277
+ formatMessage: this.props.intl.formatMessage,
278
+ touchedField: { [id]: value },
279
+ });
280
+
281
+ this.setState({
282
+ errors,
283
+ });
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Component did mount
289
+ * @method componentDidMount
290
+ * @returns {undefined}
291
+ */
292
+ componentDidMount() {
293
+ this.setState({ isClient: true });
294
+ }
295
+
296
+ static getDerivedStateFromProps(props, state) {
297
+ let newState = { ...state };
298
+ if (!props.isFormSelected) {
299
+ newState.selected = null;
300
+ }
301
+
302
+ return newState;
303
+ }
304
+
305
+ /**
306
+ * Change field handler
307
+ * Remove errors for changed field
308
+ * @method onChangeField
309
+ * @param {string} id Id of the field
310
+ * @param {*} value Value of the field
311
+ * @returns {undefined}
312
+ */
313
+ onChangeField(id, value) {
314
+ this.setState((prevState) => {
315
+ const { errors, formData } = prevState;
316
+ delete errors[id];
317
+ return {
318
+ errors,
319
+ formData: {
320
+ ...formData,
321
+ // We need to catch also when the value equals false this fixes #888
322
+ [id]:
323
+ value || (value !== undefined && isBoolean(value)) ? value : null,
324
+ },
325
+ };
326
+ });
327
+ }
328
+
329
+ /**
330
+ * Select block handler
331
+ * @method onSelectBlock
332
+ * @param {string} id Id of the field
333
+ * @param {string} isMultipleSelection true if multiple blocks are selected
334
+ * @returns {undefined}
335
+ */
336
+ onSelectBlock(id, isMultipleSelection, event) {
337
+ let multiSelected = [];
338
+ let selected = id;
339
+
340
+ if (isMultipleSelection) {
341
+ selected = null;
342
+ const blocksLayoutFieldname = getBlocksLayoutFieldname(
343
+ this.state.formData,
344
+ );
345
+
346
+ const blocks_layout = this.state.formData[blocksLayoutFieldname].items;
347
+
348
+ if (event.shiftKey) {
349
+ const anchor =
350
+ this.state.multiSelected.length > 0
351
+ ? blocks_layout.indexOf(this.state.multiSelected[0])
352
+ : blocks_layout.indexOf(this.state.selected);
353
+ const focus = blocks_layout.indexOf(id);
354
+
355
+ if (anchor === focus) {
356
+ multiSelected = [id];
357
+ } else if (focus > anchor) {
358
+ multiSelected = [...blocks_layout.slice(anchor, focus + 1)];
359
+ } else {
360
+ multiSelected = [...blocks_layout.slice(focus, anchor + 1)];
361
+ }
362
+ }
363
+
364
+ if ((event.ctrlKey || event.metaKey) && !event.shiftKey) {
365
+ multiSelected = this.state.multiSelected || [];
366
+ if (!this.state.multiSelected.includes(this.state.selected)) {
367
+ multiSelected = [...multiSelected, this.state.selected];
368
+ selected = null;
369
+ }
370
+ if (this.state.multiSelected.includes(id)) {
371
+ selected = null;
372
+ multiSelected = without(multiSelected, id);
373
+ } else {
374
+ multiSelected = [...multiSelected, id];
375
+ }
376
+ }
377
+ }
378
+
379
+ this.setState({
380
+ selected,
381
+ multiSelected,
382
+ });
383
+
384
+ if (this.props.onSelectForm) {
385
+ if (event) event.nativeEvent.stopImmediatePropagation();
386
+ this.props.onSelectForm();
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Cancel handler
392
+ * It prevents event from triggering submit, reset form if props.resetAfterSubmit
393
+ * and calls this.props.onCancel
394
+ * @method onCancel
395
+ * @param {Object} event Event object.
396
+ * @returns {undefined}
397
+ */
398
+ onCancel(event) {
399
+ if (event) {
400
+ event.preventDefault();
401
+ }
402
+ if (this.props.resetAfterSubmit) {
403
+ this.setState({
404
+ formData: this.props.formData,
405
+ });
406
+ }
407
+ this.props.onCancel(event);
408
+ }
409
+
410
+ /**
411
+ * Submit handler also validate form and collect errors
412
+ * @method onSubmit
413
+ * @param {Object} event Event object.
414
+ * @returns {undefined}
415
+ */
416
+ onSubmit(event) {
417
+ if (event) {
418
+ event.preventDefault();
419
+ }
420
+
421
+ const errors = FormValidation.validateFieldsPerFieldset({
422
+ schema: this.props.schema,
423
+ formData: this.state.formData,
424
+ formatMessage: this.props.intl.formatMessage,
425
+ });
426
+
427
+ if (keys(errors).length > 0) {
428
+ const activeIndex = FormValidation.showFirstTabWithErrors({
429
+ errors,
430
+ schema: this.props.schema,
431
+ });
432
+ this.setState(
433
+ {
434
+ errors,
435
+ activeIndex,
436
+ },
437
+ () => {
438
+ Object.keys(errors).forEach((err) =>
439
+ toast.error(
440
+ <Toast error title={err} content={errors[err].join(', ')} />,
441
+ ),
442
+ );
443
+ },
444
+ );
445
+ // Changes the focus to the metadata tab in the sidebar if error
446
+ this.props.setSidebarTab(0);
447
+ } else {
448
+ // Get only the values that have been modified (Edit forms), send all in case that
449
+ // it's an add form
450
+ if (this.props.isEditForm) {
451
+ this.props.onSubmit(this.getOnlyFormModifiedValues());
452
+ } else {
453
+ this.props.onSubmit(this.state.formData);
454
+ }
455
+ if (this.props.resetAfterSubmit) {
456
+ this.setState({
457
+ formData: this.props.formData,
458
+ });
459
+ }
460
+ }
461
+ }
462
+
463
+ /**
464
+ * getOnlyFormModifiedValues handler
465
+ * It returns only the values of the fields that are have really changed since the
466
+ * form was loaded. Useful for edit forms and PATCH operations, when we only want to
467
+ * send the changed data.
468
+ * @method getOnlyFormModifiedValues
469
+ * @param {Object} event Event object.
470
+ * @returns {undefined}
471
+ */
472
+ getOnlyFormModifiedValues = () => {
473
+ const fieldsModified = Object.keys(
474
+ difference(this.state.formData, this.state.initialFormData),
475
+ );
476
+ return {
477
+ ...pickBy(this.state.formData, (value, key) =>
478
+ fieldsModified.includes(key),
479
+ ),
480
+ ...(this.state.formData['@static_behaviors'] && {
481
+ '@static_behaviors': this.state.formData['@static_behaviors'],
482
+ }),
483
+ };
484
+ };
485
+
486
+ /**
487
+ * Removed blocks and blocks_layout fields from the form.
488
+ * @method removeBlocksLayoutFields
489
+ * @param {object} schema The schema definition of the form.
490
+ * @returns A modified copy of the given schema.
491
+ */
492
+ removeBlocksLayoutFields = (schema) => {
493
+ const newSchema = { ...schema };
494
+ const layoutFieldsetIndex = findIndex(
495
+ newSchema.fieldsets,
496
+ (fieldset) => fieldset.id === 'layout',
497
+ );
498
+ if (layoutFieldsetIndex > -1) {
499
+ const layoutFields = newSchema.fieldsets[layoutFieldsetIndex].fields;
500
+ newSchema.fieldsets[layoutFieldsetIndex].fields = layoutFields.filter(
501
+ (field) => field !== 'blocks' && field !== 'blocks_layout',
502
+ );
503
+ if (newSchema.fieldsets[layoutFieldsetIndex].fields.length === 0) {
504
+ newSchema.fieldsets = [
505
+ ...newSchema.fieldsets.slice(0, layoutFieldsetIndex),
506
+ ...newSchema.fieldsets.slice(layoutFieldsetIndex + 1),
507
+ ];
508
+ }
509
+ }
510
+ return newSchema;
511
+ };
512
+
513
+ /**
514
+ * Render method.
515
+ * @method render
516
+ * @returns {string} Markup for the component.
517
+ */
518
+ render() {
519
+ const { settings } = config;
520
+ const { schema: originalSchema, onCancel, onSubmit } = this.props;
521
+ const { formData } = this.state;
522
+ const schema = this.removeBlocksLayoutFields(originalSchema);
523
+
524
+ return this.props.visual ? (
525
+ // Removing this from SSR is important, since react-beautiful-dnd supports SSR,
526
+ // but draftJS don't like it much and the hydration gets messed up
527
+ this.state.isClient && (
528
+ <div className="ui container">
529
+ <BlocksToolbar
530
+ formData={this.state.formData}
531
+ selectedBlock={this.state.selected}
532
+ selectedBlocks={this.state.multiSelected}
533
+ onChangeBlocks={(newBlockData) =>
534
+ this.setState({
535
+ formData: {
536
+ ...formData,
537
+ ...newBlockData,
538
+ },
539
+ })
540
+ }
541
+ onSetSelectedBlocks={(blockIds) =>
542
+ this.setState({ multiSelected: blockIds })
543
+ }
544
+ onSelectBlock={this.onSelectBlock}
545
+ />
546
+ <UndoToolbar
547
+ state={{
548
+ formData: this.state.formData,
549
+ selected: this.state.selected,
550
+ multiSelected: this.state.multiSelected,
551
+ }}
552
+ enableHotKeys
553
+ onUndoRedo={({ state }) => this.setState(state)}
554
+ />
555
+ <BlocksForm
556
+ onChangeFormData={(newFormData) =>
557
+ this.setState({
558
+ formData: {
559
+ ...formData,
560
+ ...newFormData,
561
+ },
562
+ })
563
+ }
564
+ onChangeField={this.onChangeField}
565
+ onSelectBlock={this.onSelectBlock}
566
+ properties={formData}
567
+ pathname={this.props.pathname}
568
+ selectedBlock={this.state.selected}
569
+ multiSelected={this.state.multiSelected}
570
+ manage={this.props.isAdminForm}
571
+ allowedBlocks={this.props.allowedBlocks}
572
+ showRestricted={this.props.showRestricted}
573
+ editable={this.props.editable}
574
+ isMainForm={this.props.editable}
575
+ />
576
+ {this.state.isClient && this.props.editable && (
577
+ <Portal
578
+ node={__CLIENT__ && document.getElementById('sidebar-metadata')}
579
+ >
580
+ <UiForm
581
+ method="post"
582
+ onSubmit={this.onSubmit}
583
+ error={keys(this.state.errors).length > 0}
584
+ >
585
+ {schema &&
586
+ map(schema.fieldsets, (item) => [
587
+ <Segment
588
+ secondary
589
+ attached
590
+ className={`fieldset-${item.id}`}
591
+ key={item.title}
592
+ >
593
+ {item.title}
594
+ </Segment>,
595
+ <Segment attached key={`fieldset-contents-${item.title}`}>
596
+ {map(item.fields, (field, index) => (
597
+ <Field
598
+ {...schema.properties[field]}
599
+ id={field}
600
+ fieldSet={item.title.toLowerCase()}
601
+ formData={this.state.formData}
602
+ focus={false}
603
+ value={this.state.formData?.[field]}
604
+ required={schema.required.indexOf(field) !== -1}
605
+ onChange={this.onChangeField}
606
+ onBlur={this.onBlurField}
607
+ onClick={this.onClickInput}
608
+ key={field}
609
+ error={this.state.errors[field]}
610
+ />
611
+ ))}
612
+ </Segment>,
613
+ ])}
614
+ </UiForm>
615
+ </Portal>
616
+ )}
617
+ </div>
618
+ )
619
+ ) : (
620
+ <Container>
621
+ <UiForm
622
+ method="post"
623
+ onSubmit={this.onSubmit}
624
+ error={keys(this.state.errors).length > 0}
625
+ className={settings.verticalFormTabs ? 'vertical-form' : ''}
626
+ >
627
+ <fieldset className="invisible" disabled={!this.props.editable}>
628
+ <Segment.Group raised>
629
+ {schema && schema.fieldsets.length > 1 && (
630
+ <>
631
+ {settings.verticalFormTabs && this.props.title && (
632
+ <Segment secondary attached key={this.props.title}>
633
+ {this.props.title}
634
+ </Segment>
635
+ )}
636
+ <Tab
637
+ menu={{
638
+ secondary: true,
639
+ pointing: true,
640
+ attached: true,
641
+ tabular: true,
642
+ className: 'formtabs',
643
+ vertical: settings.verticalFormTabs,
644
+ }}
645
+ grid={{ paneWidth: 9, tabWidth: 3, stackable: true }}
646
+ onTabChange={this.onTabChange}
647
+ activeIndex={this.state.activeIndex}
648
+ panes={map(schema.fieldsets, (item) => ({
649
+ menuItem: item.title,
650
+ render: () => [
651
+ !settings.verticalFormTabs && this.props.title && (
652
+ <Segment secondary attached key={this.props.title}>
653
+ {this.props.title}
654
+ </Segment>
655
+ ),
656
+ ...map(item.fields, (field, index) => (
657
+ <Field
658
+ {...schema.properties[field]}
659
+ id={field}
660
+ formData={this.state.formData}
661
+ fieldSet={item.title.toLowerCase()}
662
+ focus={index === 0}
663
+ value={this.state.formData?.[field]}
664
+ required={schema.required.indexOf(field) !== -1}
665
+ onChange={this.onChangeField}
666
+ onBlur={this.onBlurField}
667
+ onClick={this.onClickInput}
668
+ key={field}
669
+ error={this.state.errors[field]}
670
+ />
671
+ )),
672
+ ],
673
+ }))}
674
+ />
675
+ </>
676
+ )}
677
+ {schema && schema.fieldsets.length === 1 && (
678
+ <Segment>
679
+ {this.props.title && (
680
+ <Segment className="primary">
681
+ <h1 style={{ fontSize: '16px' }}> {this.props.title}</h1>
682
+ </Segment>
683
+ )}
684
+ {this.props.description && (
685
+ <Segment secondary>{this.props.description}</Segment>
686
+ )}
687
+ {keys(this.state.errors).length > 0 && (
688
+ <Message
689
+ icon="warning"
690
+ negative
691
+ attached
692
+ header={this.props.intl.formatMessage(messages.error)}
693
+ content={this.props.intl.formatMessage(
694
+ messages.thereWereSomeErrors,
695
+ )}
696
+ />
697
+ )}
698
+ {this.props.error && (
699
+ <Message
700
+ icon="warning"
701
+ negative
702
+ attached
703
+ header={this.props.intl.formatMessage(messages.error)}
704
+ content={this.props.error.message}
705
+ />
706
+ )}
707
+ {map(schema.fieldsets[0].fields, (field) => (
708
+ <Field
709
+ {...schema.properties[field]}
710
+ id={field}
711
+ value={this.state.formData?.[field]}
712
+ required={schema.required.indexOf(field) !== -1}
713
+ onChange={this.onChangeField}
714
+ onBlur={this.onBlurField}
715
+ onClick={this.onClickInput}
716
+ key={field}
717
+ error={this.state.errors[field]}
718
+ />
719
+ ))}
720
+ </Segment>
721
+ )}
722
+ {!this.props.hideActions && (
723
+ <Segment className="actions" clearing>
724
+ {onSubmit && (
725
+ <Button
726
+ basic
727
+ icon
728
+ primary
729
+ floated="right"
730
+ type="submit"
731
+ aria-label={
732
+ this.props.submitLabel
733
+ ? this.props.submitLabel
734
+ : this.props.intl.formatMessage(messages.save)
735
+ }
736
+ title={
737
+ this.props.submitLabel
738
+ ? this.props.submitLabel
739
+ : this.props.intl.formatMessage(messages.save)
740
+ }
741
+ loading={this.props.loading}
742
+ >
743
+ <Icon className="circled" name={aheadSVG} size="30px" />
744
+ </Button>
745
+ )}
746
+ {onCancel && (
747
+ <Button
748
+ basic
749
+ icon
750
+ secondary
751
+ aria-label={this.props.intl.formatMessage(
752
+ messages.cancel,
753
+ )}
754
+ title={this.props.intl.formatMessage(messages.cancel)}
755
+ floated="right"
756
+ onClick={this.onCancel}
757
+ >
758
+ <Icon className="circled" name={clearSVG} size="30px" />
759
+ </Button>
760
+ )}
761
+ </Segment>
762
+ )}
763
+ </Segment.Group>
764
+ </fieldset>
765
+ </UiForm>
766
+ </Container>
767
+ );
768
+ }
769
+ }
770
+
771
+ const FormIntl = injectIntl(Form, { forwardRef: true });
772
+
773
+ export default compose(
774
+ connect(null, { setSidebarTab }, null, { forwardRef: true }),
775
+ )(FormIntl);