Skip to content

Commit 8663870

Browse files
LukshioMariaAga
authored andcommitted
Fixes #38475 - update BookmarkForm with Modal to PF5
1 parent 0ba2417 commit 8663870

File tree

10 files changed

+341
-104
lines changed

10 files changed

+341
-104
lines changed

webpack/assets/javascripts/react_app/components/BookmarkForm/BookmarkForm.js

Lines changed: 180 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,206 @@
1-
import React from 'react';
1+
import React, { useEffect, useState } from 'react';
22
import PropTypes from 'prop-types';
3-
import * as Yup from 'yup';
3+
import { ExclamationCircleIcon } from '@patternfly/react-icons';
4+
import {
5+
Button,
6+
Form,
7+
FormGroup,
8+
TextInput,
9+
Checkbox,
10+
ActionGroup,
11+
HelperText,
12+
HelperTextItem,
13+
FormHelperText,
14+
} from '@patternfly/react-core';
415
import { useDispatch } from 'react-redux';
5-
import { noop } from '../../common/helpers';
6-
import ForemanForm from '../common/forms/ForemanForm';
7-
import TextField from '../common/forms/TextField';
8-
import { translate as __ } from '../../common/I18n';
9-
import { maxLengthMsg, requiredMsg } from '../common/forms/validators';
10-
import { submitForm } from '../../redux/actions/common/forms';
16+
import { sprintf, translate as __ } from '../../common/I18n';
17+
import { APIActions } from '../../redux/API';
18+
import './bookmarkForm.css';
1119

1220
const BookmarkForm = ({
1321
url,
1422
controller,
15-
onCancel,
1623
searchQuery,
1724
setModalClosed,
1825
bookmarks,
1926
}) => {
2027
const dispatch = useDispatch();
21-
const existsNamesRegex = new RegExp(
22-
`^(?!(${bookmarks.map(({ name }) => name).join('|')})$).+`
28+
const existingNames = bookmarks.map(({ name }) => name);
29+
30+
const NAME_MAX_LENGTH = 255;
31+
const NAME_MIN_LENGTH = 1;
32+
const SUCCESS_TYPE = 'success';
33+
const ERROR_TYPE = 'error';
34+
const BLANK_ERROR = "Can't be blank";
35+
36+
const nameTooLongMsg = sprintf(
37+
__('Name is too long, max %s'),
38+
NAME_MAX_LENGTH
2339
);
24-
const bookmarkFormSchema = Yup.object().shape({
25-
name: Yup.string()
26-
.max(...maxLengthMsg(254))
27-
.required(requiredMsg())
28-
.matches(existsNamesRegex, {
29-
excludeEmptyString: true,
30-
message: __('name already exists'),
31-
}),
32-
query: Yup.string()
33-
.max(...maxLengthMsg(4096))
34-
.required(requiredMsg()),
35-
});
3640

37-
const handleSubmit = (values, actions) =>
41+
const nameExists = __('Name already exists');
42+
const nameShort = __(BLANK_ERROR);
43+
const blankInputErrMsg = __(BLANK_ERROR);
44+
45+
const [name, setName] = useState('');
46+
const [query, setQuery] = useState(searchQuery);
47+
const [isPublic, setIsPublic] = useState(true);
48+
49+
const [nameErrorMsg, setNameErrorMsg] = useState('');
50+
const [nameValidated, setNameValidated] = useState('');
51+
const [queryErrorMsg, setQueryErrorMsg] = useState('');
52+
const [queryValidated, setQueryValidated] = useState('');
53+
54+
const validateName = value => {
55+
if (value.length > NAME_MAX_LENGTH) {
56+
setNameValidated(ERROR_TYPE);
57+
setNameErrorMsg(nameTooLongMsg);
58+
} else if (value.length < NAME_MIN_LENGTH) {
59+
setNameValidated(ERROR_TYPE);
60+
setNameErrorMsg(nameShort);
61+
} else if (existingNames.includes(value)) {
62+
setNameValidated(ERROR_TYPE);
63+
setNameErrorMsg(nameExists);
64+
} else {
65+
setNameValidated(SUCCESS_TYPE);
66+
}
67+
};
68+
69+
const validateQueryLength = value => {
70+
if (value.length < 1) {
71+
setQueryValidated(ERROR_TYPE);
72+
setQueryErrorMsg(blankInputErrMsg);
73+
} else {
74+
setQueryValidated(SUCCESS_TYPE);
75+
}
76+
};
77+
78+
const handleSubmit = () => {
3879
dispatch(
39-
submitForm({
80+
APIActions.post({
81+
key: 'BOOKMARKS_FORM_SUBMITTED',
4082
url,
41-
values: { ...values, controller },
42-
item: 'Bookmarks',
43-
message: __('Bookmark was successfully created.'),
44-
successCallback: setModalClosed,
45-
actions,
83+
params: {
84+
public: isPublic,
85+
controller,
86+
query,
87+
name,
88+
},
89+
handleSuccess: setModalClosed,
90+
successToast: () => __('Bookmark created.'),
91+
errorToast: ({ response }) =>
92+
// eslint-disable-next-line camelcase
93+
response?.data?.error?.full_messages?.[0] || response,
4694
})
4795
);
96+
};
97+
98+
const isValidated =
99+
nameValidated === SUCCESS_TYPE && queryValidated === SUCCESS_TYPE;
100+
101+
useEffect(() => {
102+
if (searchQuery !== '') {
103+
validateQueryLength(query);
104+
}
105+
});
106+
107+
const handleNameChange = (_event, value) => {
108+
validateName(value);
109+
setName(value);
110+
};
111+
112+
const handleQueryChange = (_event, value) => {
113+
validateQueryLength(value);
114+
setQuery(value);
115+
};
116+
117+
const handleIsPublicChange = (_event, value) => {
118+
setIsPublic(value);
119+
};
48120

49121
return (
50-
<ForemanForm
51-
onSubmit={handleSubmit}
52-
initialValues={{
53-
public: true,
54-
query: searchQuery,
55-
}}
56-
validationSchema={bookmarkFormSchema}
57-
onCancel={onCancel}
58-
>
59-
<TextField name="name" type="text" required="true" label={__('Name')} />
60-
<TextField
61-
name="query"
62-
type="textarea"
63-
required="true"
64-
label={__('Query')}
65-
inputClassName="col-md-8"
66-
/>
67-
<TextField name="public" type="checkbox" label={__('Public')} />
68-
</ForemanForm>
122+
<>
123+
<Form>
124+
<FormGroup label={__('Name')}>
125+
<TextInput
126+
ouiaId="name-input"
127+
id="name-input"
128+
isRequired
129+
type="text"
130+
name="name"
131+
value={name}
132+
onChange={handleNameChange}
133+
validated={nameValidated}
134+
/>
135+
{nameValidated === ERROR_TYPE && (
136+
<FormHelperText>
137+
<HelperText>
138+
<HelperTextItem
139+
icon={<ExclamationCircleIcon />}
140+
variant={nameValidated}
141+
>
142+
{nameErrorMsg}
143+
</HelperTextItem>
144+
</HelperText>
145+
</FormHelperText>
146+
)}
147+
</FormGroup>
148+
<FormGroup label={__('Query')}>
149+
<TextInput
150+
ouiaId="query-input"
151+
id="query-input"
152+
isRequired
153+
type="text"
154+
name="query"
155+
value={query}
156+
onChange={handleQueryChange}
157+
validated={queryValidated}
158+
/>
159+
{queryValidated === ERROR_TYPE && (
160+
<FormHelperText>
161+
<HelperText>
162+
<HelperTextItem
163+
icon={<ExclamationCircleIcon />}
164+
variant={queryValidated}
165+
>
166+
{queryErrorMsg}
167+
</HelperTextItem>
168+
</HelperText>
169+
</FormHelperText>
170+
)}
171+
</FormGroup>
172+
<Checkbox
173+
ouiaId="isPublic-checkbox"
174+
id="isPublic-checkbox"
175+
label={__('Public')}
176+
isLabelBeforeButton
177+
isLabelWrapped
178+
isChecked={isPublic}
179+
onChange={handleIsPublicChange}
180+
/>
181+
<ActionGroup>
182+
<Button
183+
ouiaId="submit-btn"
184+
variant="primary"
185+
isDisabled={!isValidated}
186+
onClick={handleSubmit}
187+
>
188+
{__('Submit')}
189+
</Button>
190+
<Button
191+
ouiaId="cancel-btn"
192+
variant="secondary"
193+
onClick={setModalClosed}
194+
>
195+
{__('Cancel')}
196+
</Button>
197+
</ActionGroup>
198+
</Form>
199+
</>
69200
);
70201
};
71202

72203
BookmarkForm.propTypes = {
73-
onCancel: PropTypes.func,
74204
controller: PropTypes.string.isRequired,
75205
url: PropTypes.string.isRequired,
76206
setModalClosed: PropTypes.func.isRequired,
@@ -79,7 +209,6 @@ BookmarkForm.propTypes = {
79209
};
80210

81211
BookmarkForm.defaultProps = {
82-
onCancel: noop,
83212
bookmarks: [],
84213
};
85214

webpack/assets/javascripts/react_app/components/BookmarkForm/SearchModal.js

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,51 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
3-
import ForemanModal from '../ForemanModal';
3+
import { Modal, ModalHeader, ModalBody } from '@patternfly/react-core/next';
44
import { translate as __ } from '../../common/I18n';
5-
import { noop } from '../../common/helpers';
65
import BookmarkForm from './BookmarkForm';
7-
import { getBookmarksModalId } from '../PF4/Bookmarks/BookmarksHelpers';
86

97
const SearchModal = ({
10-
id,
118
setModalClosed,
12-
onEnter,
139
title,
1410
controller,
1511
url,
1612
bookmarks,
1713
searchQuery,
14+
isOpened,
1815
}) => (
19-
<ForemanModal
20-
id={getBookmarksModalId(id)}
21-
title={title}
22-
enforceFocus
23-
onEnter={onEnter}
16+
<Modal
17+
variant="small"
18+
ouiaId="bookmark-modal"
19+
aria-label="bookmark-modal"
20+
isOpen={isOpened}
21+
onClose={setModalClosed}
2422
>
25-
<BookmarkForm
26-
id={id}
27-
controller={controller}
28-
url={url}
29-
setModalClosed={setModalClosed}
30-
onCancel={setModalClosed}
31-
bookmarks={bookmarks}
32-
searchQuery={searchQuery}
33-
/>
34-
</ForemanModal>
23+
<ModalHeader title={title} />
24+
<ModalBody>
25+
<BookmarkForm
26+
controller={controller}
27+
url={url}
28+
setModalClosed={setModalClosed}
29+
onCancel={setModalClosed}
30+
bookmarks={bookmarks}
31+
searchQuery={searchQuery}
32+
/>
33+
</ModalBody>
34+
</Modal>
3535
);
3636

3737
SearchModal.propTypes = {
38-
id: PropTypes.string,
3938
controller: PropTypes.string.isRequired,
4039
url: PropTypes.string.isRequired,
4140
title: PropTypes.string,
42-
onEnter: PropTypes.func,
4341
setModalClosed: PropTypes.func.isRequired,
4442
bookmarks: PropTypes.array,
4543
searchQuery: PropTypes.string.isRequired,
44+
isOpened: PropTypes.bool.isRequired,
4645
};
4746

4847
SearchModal.defaultProps = {
49-
id: '',
5048
title: __('Create Bookmark'),
51-
onEnter: noop,
5249
bookmarks: [],
5350
};
5451

0 commit comments

Comments
 (0)