@availity/feedback 0.0.0-t325224b2 → 1.0.0
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/CHANGELOG.md +2005 -0
- package/README.md +13 -64
- package/index.d.ts +3 -6
- package/index.js +3 -6
- package/jest.config.js +7 -0
- package/package.json +40 -25
- package/project.json +39 -0
- package/src/Feedback.d.ts +25 -0
- package/src/Feedback.js +107 -0
- package/{FeedbackButton.d.ts → src/FeedbackButton.d.ts} +1 -1
- package/src/FeedbackButton.js +33 -0
- package/{Feedback.d.ts → src/FeedbackDropdown.d.ts} +2 -2
- package/src/FeedbackDropdown.js +69 -0
- package/src/FeedbackForm.d.ts +31 -0
- package/src/FeedbackForm.js +306 -0
- package/src/FeedbackModal.d.ts +9 -0
- package/src/FeedbackModal.js +49 -0
- package/src/SmileField.js +76 -0
- package/src/SupportDropdown.js +37 -0
- package/src/SupportForm.js +149 -0
- package/src/SupportModal.js +40 -0
- package/tsconfig.json +5 -0
- package/tsconfig.spec.json +10 -0
- package/Feedback.js +0 -48
- package/FeedbackButton.js +0 -36
- package/FeedbackForm.d.ts +0 -21
- package/FeedbackForm.js +0 -137
- package/LICENSE +0 -20
- package/tests/Feedback.test.js +0 -59
- package/tests/FeedbackForm.test.js +0 -99
- package/tests/__snapshots__/Feedback.test.js.snap +0 -610
- package/tests/__snapshots__/FeedbackForm.test.js.snap +0 -435
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Button, ModalBody, ModalHeader, ModalFooter, FormGroup } from 'reactstrap';
|
|
4
|
+
import { avLogMessagesApiV2, avRegionsApi } from '@availity/api-axios';
|
|
5
|
+
import { Form, Field } from '@availity/form';
|
|
6
|
+
import { SelectField } from '@availity/select';
|
|
7
|
+
import * as yup from 'yup';
|
|
8
|
+
|
|
9
|
+
import SmileField from './SmileField';
|
|
10
|
+
|
|
11
|
+
yup.addMethod(yup.string, 'isRequired', function format(isRequired, msg) {
|
|
12
|
+
return this.test({
|
|
13
|
+
name: 'dateRange',
|
|
14
|
+
exclusive: true,
|
|
15
|
+
message: msg || 'This field is required.',
|
|
16
|
+
test(value) {
|
|
17
|
+
if (isRequired) {
|
|
18
|
+
return value !== undefined;
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const fieldStyles = { resize: 'none' };
|
|
26
|
+
const inlineStyles = { display: 'inline-block', margin: 0 };
|
|
27
|
+
|
|
28
|
+
const FeedbackForm = ({
|
|
29
|
+
name,
|
|
30
|
+
onClose,
|
|
31
|
+
faceOptions,
|
|
32
|
+
aboutOptions = [],
|
|
33
|
+
aboutLabel = 'This is about',
|
|
34
|
+
onFeedbackSent,
|
|
35
|
+
prompt,
|
|
36
|
+
additionalComments = false,
|
|
37
|
+
staticFields,
|
|
38
|
+
analytics = avLogMessagesApiV2,
|
|
39
|
+
modalHeaderProps,
|
|
40
|
+
showSupport = false,
|
|
41
|
+
setSupportIsActive,
|
|
42
|
+
autoFocusFeedbackButton,
|
|
43
|
+
modal = true,
|
|
44
|
+
...formProps
|
|
45
|
+
}) => {
|
|
46
|
+
const [active, setActive] = useState(null);
|
|
47
|
+
const [sent, setSent] = useState(null);
|
|
48
|
+
const [loading, setLoading] = useState(null);
|
|
49
|
+
const ref = useRef();
|
|
50
|
+
|
|
51
|
+
const sendFeedback = async ({ smileField, ...values }) => {
|
|
52
|
+
setLoading(true);
|
|
53
|
+
const response = await avRegionsApi.getCurrentRegion();
|
|
54
|
+
|
|
55
|
+
await analytics.info({
|
|
56
|
+
surveyId: `${name.replaceAll(/\s/g, '_')}_Smile_Survey`,
|
|
57
|
+
smileLocation: `${name}`,
|
|
58
|
+
smile: `icon-${smileField.icon}`,
|
|
59
|
+
url: window.location.href,
|
|
60
|
+
region: response.data.regions[0] && response.data.regions[0].id,
|
|
61
|
+
userAgent: window.navigator.userAgent,
|
|
62
|
+
submitTime: new Date(),
|
|
63
|
+
...values, // Spread the form values onto the logger
|
|
64
|
+
...staticFields, // Spread the static key value pairs onto the logger
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
setSent(values);
|
|
68
|
+
setLoading(false);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Close the Modal once sent after 2 seconds
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (sent) {
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
if (onClose) {
|
|
76
|
+
onClose(); // Mostly for Screen Reader use but a nice to have for all
|
|
77
|
+
}
|
|
78
|
+
if (onFeedbackSent) {
|
|
79
|
+
for (const key of Object.keys(sent)) {
|
|
80
|
+
if (sent[key] === undefined) {
|
|
81
|
+
delete sent[key];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
onFeedbackSent({
|
|
86
|
+
active: active.icon,
|
|
87
|
+
...sent,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}, 2000);
|
|
91
|
+
}
|
|
92
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
93
|
+
}, [sent]);
|
|
94
|
+
|
|
95
|
+
return sent ? (
|
|
96
|
+
<ModalHeader
|
|
97
|
+
role="status"
|
|
98
|
+
id="feedback-form-header"
|
|
99
|
+
tabIndex="0"
|
|
100
|
+
className="d-flex justify-content-center"
|
|
101
|
+
{...modalHeaderProps}
|
|
102
|
+
>
|
|
103
|
+
Thank you for your feedback.
|
|
104
|
+
</ModalHeader>
|
|
105
|
+
) : (
|
|
106
|
+
<>
|
|
107
|
+
<ModalHeader
|
|
108
|
+
id="feedback-form-header"
|
|
109
|
+
role="heading"
|
|
110
|
+
aria-level="2"
|
|
111
|
+
className="h5"
|
|
112
|
+
tag="div"
|
|
113
|
+
{...modalHeaderProps}
|
|
114
|
+
>
|
|
115
|
+
{prompt || `Tell us what you think about ${name}`}
|
|
116
|
+
</ModalHeader>
|
|
117
|
+
<Form
|
|
118
|
+
innerRef={ref}
|
|
119
|
+
aria-label="Feedback Form"
|
|
120
|
+
aria-describedby="feedback-form-header"
|
|
121
|
+
role="form"
|
|
122
|
+
onKeyDown={({ key }) => key === 'Escape' && onClose()}
|
|
123
|
+
data-testid="feedback-form"
|
|
124
|
+
initialValues={{
|
|
125
|
+
'face-options': undefined,
|
|
126
|
+
additionalFeedback: undefined,
|
|
127
|
+
feedback: undefined,
|
|
128
|
+
feedbackApp: undefined,
|
|
129
|
+
smileField: undefined,
|
|
130
|
+
}}
|
|
131
|
+
validationSchema={yup.object().shape({
|
|
132
|
+
feedback: yup.string().max(200, 'Feedback cannot exceed 200 characters.').required('This field is required.'),
|
|
133
|
+
additionalFeedback: yup.string().max(200, 'Additional Feedback cannot exceed 200 characters.'),
|
|
134
|
+
smileField: yup
|
|
135
|
+
.object()
|
|
136
|
+
.shape({
|
|
137
|
+
icon: yup.string().required(),
|
|
138
|
+
description: yup.string(),
|
|
139
|
+
label: yup.string(),
|
|
140
|
+
})
|
|
141
|
+
.required('This field is required.'),
|
|
142
|
+
feedbackApp: yup.string().isRequired(aboutOptions.length > 0, 'This field is required.'),
|
|
143
|
+
})}
|
|
144
|
+
{...formProps}
|
|
145
|
+
onSubmit={(values) => sendFeedback(values)}
|
|
146
|
+
>
|
|
147
|
+
<ModalBody>
|
|
148
|
+
<FormGroup
|
|
149
|
+
size="lg"
|
|
150
|
+
id="face-options"
|
|
151
|
+
role="group"
|
|
152
|
+
aria-labelledby="feedback-form-header"
|
|
153
|
+
data-testid="face-options"
|
|
154
|
+
className="d-flex flex-row justify-content-between"
|
|
155
|
+
>
|
|
156
|
+
<SmileField
|
|
157
|
+
options={faceOptions}
|
|
158
|
+
name="smileField"
|
|
159
|
+
onChange={(option) => setActive(option)}
|
|
160
|
+
onClose={onClose}
|
|
161
|
+
autoFocusFeedbackButton={autoFocusFeedbackButton}
|
|
162
|
+
modal={modal}
|
|
163
|
+
/>
|
|
164
|
+
</FormGroup>
|
|
165
|
+
|
|
166
|
+
{active ? (
|
|
167
|
+
<>
|
|
168
|
+
{aboutOptions.length > 0 && (
|
|
169
|
+
<SelectField
|
|
170
|
+
name="feedbackApp"
|
|
171
|
+
id="about-options"
|
|
172
|
+
data-testid="about-options"
|
|
173
|
+
label={aboutLabel}
|
|
174
|
+
options={aboutOptions}
|
|
175
|
+
/>
|
|
176
|
+
)}
|
|
177
|
+
|
|
178
|
+
<Field
|
|
179
|
+
type="textarea"
|
|
180
|
+
name="feedback"
|
|
181
|
+
label={(active && active.label) || 'Feedback? Requests? Defects?'}
|
|
182
|
+
style={fieldStyles}
|
|
183
|
+
rows="2"
|
|
184
|
+
/>
|
|
185
|
+
{additionalComments && (
|
|
186
|
+
<Field
|
|
187
|
+
type="textarea"
|
|
188
|
+
name="additionalFeedback"
|
|
189
|
+
label="Additional Comments... (Optional)"
|
|
190
|
+
style={fieldStyles}
|
|
191
|
+
rows="2"
|
|
192
|
+
/>
|
|
193
|
+
)}
|
|
194
|
+
</>
|
|
195
|
+
) : null}
|
|
196
|
+
</ModalBody>
|
|
197
|
+
|
|
198
|
+
<ModalFooter>
|
|
199
|
+
{showSupport ? (
|
|
200
|
+
<>
|
|
201
|
+
<span className="d-none d-md-block" style={inlineStyles}>
|
|
202
|
+
Need Help?
|
|
203
|
+
</span>
|
|
204
|
+
<Button
|
|
205
|
+
className="pl-0"
|
|
206
|
+
onClick={() => setSupportIsActive(true)}
|
|
207
|
+
color="link"
|
|
208
|
+
type="button"
|
|
209
|
+
onKeyDown={({ key }) => key === 'Enter' && setSupportIsActive(true)}
|
|
210
|
+
>
|
|
211
|
+
Open a support ticket
|
|
212
|
+
</Button>
|
|
213
|
+
</>
|
|
214
|
+
) : null}
|
|
215
|
+
|
|
216
|
+
{onClose ? (
|
|
217
|
+
<Button
|
|
218
|
+
type="button"
|
|
219
|
+
onClick={onClose}
|
|
220
|
+
color="secondary"
|
|
221
|
+
onKeyDown={({ key, shiftKey }) => {
|
|
222
|
+
if (key === 'Enter') {
|
|
223
|
+
onClose();
|
|
224
|
+
}
|
|
225
|
+
if (key === 'Tab' && !active && !shiftKey && !modal) {
|
|
226
|
+
onClose();
|
|
227
|
+
}
|
|
228
|
+
}}
|
|
229
|
+
>
|
|
230
|
+
Close
|
|
231
|
+
</Button>
|
|
232
|
+
) : null}
|
|
233
|
+
|
|
234
|
+
<Button
|
|
235
|
+
onKeyDown={({ key, shiftKey }) => {
|
|
236
|
+
if (key === 'Enter') {
|
|
237
|
+
ref.current.submitForm();
|
|
238
|
+
}
|
|
239
|
+
if (key === 'Tab' && !shiftKey && !modal) {
|
|
240
|
+
onClose();
|
|
241
|
+
}
|
|
242
|
+
}}
|
|
243
|
+
type="submit"
|
|
244
|
+
color="primary"
|
|
245
|
+
disabled={Boolean(!active || loading)}
|
|
246
|
+
>
|
|
247
|
+
Send Feedback
|
|
248
|
+
</Button>
|
|
249
|
+
</ModalFooter>
|
|
250
|
+
</Form>
|
|
251
|
+
</>
|
|
252
|
+
);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
FeedbackForm.propTypes = {
|
|
256
|
+
/** The name of the application this feedback is for. It is used in the API request to indicate where the feedback came from. */
|
|
257
|
+
name: PropTypes.string.isRequired,
|
|
258
|
+
/** Callback for when the feedback is submitted. It is called with the feedback object. */
|
|
259
|
+
onFeedbackSent: PropTypes.func,
|
|
260
|
+
/** Array of Objects containing icon (String), description (String), and label (String) properties.
|
|
261
|
+
* Allows you to override the smiley face options which appear.
|
|
262
|
+
* Default: Smiley Face, Meh Face, and Frowny Face.
|
|
263
|
+
* Previous placeholder property removed as of v6.0.0. Use label instead. */
|
|
264
|
+
faceOptions: PropTypes.arrayOf(
|
|
265
|
+
PropTypes.shape({
|
|
266
|
+
icon: PropTypes.string,
|
|
267
|
+
description: PropTypes.string,
|
|
268
|
+
label: PropTypes.string,
|
|
269
|
+
})
|
|
270
|
+
),
|
|
271
|
+
/** Array of Objects containing value (String,Number) and label (String) properties.
|
|
272
|
+
* Allows a dropdown displaying the options provided to let the user indicate what the feedback is about. */
|
|
273
|
+
aboutOptions: PropTypes.arrayOf(
|
|
274
|
+
PropTypes.shape({
|
|
275
|
+
name: PropTypes.string,
|
|
276
|
+
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
277
|
+
})
|
|
278
|
+
),
|
|
279
|
+
/** Label text for the dropdown created via the aboutOptions prop. Default: "This is about".
|
|
280
|
+
*Previously aboutPlaceholder. All placeholders replaced with labels starting v6.0.0. */
|
|
281
|
+
aboutLabel: PropTypes.node,
|
|
282
|
+
/** When provided, a "Close" button is rendered and onClose is excuted when it's clicked. */
|
|
283
|
+
onClose: PropTypes.func,
|
|
284
|
+
/** Text that prompts the user to provider feedback. Default: "Tell us what you think about ${appName}.". */
|
|
285
|
+
prompt: PropTypes.string,
|
|
286
|
+
/** If true, shows an optional comments field below. */
|
|
287
|
+
additionalComments: PropTypes.bool,
|
|
288
|
+
/** Static (non-user-entered) key/value pairs to be sent in feedback submission. */
|
|
289
|
+
staticFields: PropTypes.object,
|
|
290
|
+
/** Props to be spread onto the <ModalHeader /> rendered inside of the <FeedbackForm />. See ModalHeader
|
|
291
|
+
*For accessibility use className instead of tag to adjust size and style of header. */
|
|
292
|
+
modalHeaderProps: PropTypes.shape({ ...ModalHeader.propTypes }),
|
|
293
|
+
/** Override the analytics instance that is passed in. Default avLogMessagesApiV2 */
|
|
294
|
+
analytics: PropTypes.shape({
|
|
295
|
+
info: PropTypes.func.isRequired,
|
|
296
|
+
}),
|
|
297
|
+
/** Toggle whether or not to show the "Open a Support ticket" link in the FeedbackForm */
|
|
298
|
+
showSupport: PropTypes.bool,
|
|
299
|
+
setSupportIsActive: PropTypes.func,
|
|
300
|
+
/** Default: true. When set to false, the first feedback button is not focused.
|
|
301
|
+
* This is to avoid issues with focus causing other elements to close (e.g. dropdowns) */
|
|
302
|
+
autoFocusFeedbackButton: PropTypes.bool,
|
|
303
|
+
modal: PropTypes.bool,
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
export default FeedbackForm;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Modal } from 'reactstrap';
|
|
4
|
+
|
|
5
|
+
import FeedbackForm from './FeedbackForm';
|
|
6
|
+
import SupportModal from './SupportModal';
|
|
7
|
+
|
|
8
|
+
const FeedbackModal = ({ isOpen, toggle, zIndex, showSupport, supportZIndex, feedbackToggle, ...formOptions }) => {
|
|
9
|
+
const [supportIsActive, setSupportIsActive] = React.useState(false);
|
|
10
|
+
|
|
11
|
+
return supportIsActive ? (
|
|
12
|
+
<SupportModal
|
|
13
|
+
supportIsActive={supportIsActive}
|
|
14
|
+
setSupportIsActive={setSupportIsActive}
|
|
15
|
+
zIndex={supportZIndex}
|
|
16
|
+
toggle={toggle}
|
|
17
|
+
feedbackToggle={feedbackToggle}
|
|
18
|
+
/>
|
|
19
|
+
) : (
|
|
20
|
+
<Modal
|
|
21
|
+
fade
|
|
22
|
+
id="feedbackModal"
|
|
23
|
+
data-testid="feedbackModal"
|
|
24
|
+
size="lg"
|
|
25
|
+
aria-labelledby="feedback-form-header"
|
|
26
|
+
isOpen={isOpen && !supportIsActive}
|
|
27
|
+
toggle={toggle}
|
|
28
|
+
zIndex={zIndex}
|
|
29
|
+
>
|
|
30
|
+
<FeedbackForm
|
|
31
|
+
onClose={toggle}
|
|
32
|
+
showSupport={showSupport}
|
|
33
|
+
setSupportIsActive={setSupportIsActive}
|
|
34
|
+
{...formOptions}
|
|
35
|
+
/>
|
|
36
|
+
</Modal>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
FeedbackModal.propTypes = {
|
|
41
|
+
isOpen: PropTypes.bool.isRequired,
|
|
42
|
+
toggle: PropTypes.func.isRequired,
|
|
43
|
+
showSupport: PropTypes.bool,
|
|
44
|
+
zIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
|
45
|
+
supportZIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
|
46
|
+
feedbackToggle: PropTypes.func,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default FeedbackModal;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { useField, useFormikContext } from 'formik';
|
|
4
|
+
|
|
5
|
+
import FeedbackButton from './FeedbackButton';
|
|
6
|
+
|
|
7
|
+
const btnStyles = { flex: 1, margin: '0 2% 0 2%' };
|
|
8
|
+
const defaultOptions = [
|
|
9
|
+
{
|
|
10
|
+
icon: 'smile',
|
|
11
|
+
description: 'Smiley face',
|
|
12
|
+
label: 'What do you like?',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
icon: 'meh',
|
|
16
|
+
description: 'Meh face',
|
|
17
|
+
label: 'What would you improve?',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
icon: 'frown',
|
|
21
|
+
description: 'Frowny face',
|
|
22
|
+
label: "What don't you like?",
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const SmileField = ({ name, options = defaultOptions, onChange, autoFocusFeedbackButton = true, onClose, modal }) => {
|
|
27
|
+
const [{ value }] = useField(name);
|
|
28
|
+
const { setFieldValue } = useFormikContext();
|
|
29
|
+
|
|
30
|
+
return options.map((option, i) => (
|
|
31
|
+
<FeedbackButton
|
|
32
|
+
autoFocus={i === 0 && autoFocusFeedbackButton}
|
|
33
|
+
onKeyDown={(e) => {
|
|
34
|
+
if (e.shiftKey && e.keyCode === 9 && i === 0 && !modal) {
|
|
35
|
+
onClose();
|
|
36
|
+
}
|
|
37
|
+
if (e.keyCode === 13) {
|
|
38
|
+
setFieldValue(name, option);
|
|
39
|
+
if (onChange) {
|
|
40
|
+
onChange(option);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}}
|
|
44
|
+
style={btnStyles}
|
|
45
|
+
key={option.icon}
|
|
46
|
+
icon={option.icon}
|
|
47
|
+
iconSize="2x"
|
|
48
|
+
value={option}
|
|
49
|
+
active={value && value.icon}
|
|
50
|
+
onClick={() => {
|
|
51
|
+
setFieldValue(name, option);
|
|
52
|
+
if (onChange) {
|
|
53
|
+
onChange(option);
|
|
54
|
+
}
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
{option.description}
|
|
58
|
+
</FeedbackButton>
|
|
59
|
+
));
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
SmileField.propTypes = {
|
|
63
|
+
name: PropTypes.string.isRequired,
|
|
64
|
+
options: PropTypes.arrayOf(
|
|
65
|
+
PropTypes.shape({
|
|
66
|
+
icon: PropTypes.string,
|
|
67
|
+
description: PropTypes.string,
|
|
68
|
+
label: PropTypes.string,
|
|
69
|
+
})
|
|
70
|
+
),
|
|
71
|
+
onChange: PropTypes.func,
|
|
72
|
+
autoFocusFeedbackButton: PropTypes.bool,
|
|
73
|
+
modal: PropTypes.bool,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export default SmileField;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { DropdownMenu } from 'reactstrap';
|
|
4
|
+
import BlockUi from '@availity/block-ui';
|
|
5
|
+
|
|
6
|
+
import SupportForm from './SupportForm';
|
|
7
|
+
|
|
8
|
+
const dropdownStyles = { width: '550px', maxWidth: '90vw', padding: 0 };
|
|
9
|
+
|
|
10
|
+
const SupportDropdown = ({ setSupportIsActive, feedbackToggle }) => {
|
|
11
|
+
const [blocking, setBlocking] = useState(false);
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<DropdownMenu
|
|
15
|
+
right
|
|
16
|
+
style={dropdownStyles}
|
|
17
|
+
positionFixed="false"
|
|
18
|
+
role="dialog"
|
|
19
|
+
aria-labelledby="support-form-header"
|
|
20
|
+
>
|
|
21
|
+
<BlockUi tag="div" keepInView blocking={blocking}>
|
|
22
|
+
<SupportForm
|
|
23
|
+
setSupportIsActive={setSupportIsActive}
|
|
24
|
+
setBlocking={setBlocking}
|
|
25
|
+
feedbackToggle={feedbackToggle}
|
|
26
|
+
/>
|
|
27
|
+
</BlockUi>
|
|
28
|
+
</DropdownMenu>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
SupportDropdown.propTypes = {
|
|
33
|
+
setSupportIsActive: PropTypes.func,
|
|
34
|
+
feedbackToggle: PropTypes.func,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default SupportDropdown;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Button, ModalBody, ModalHeader, ModalFooter } from 'reactstrap';
|
|
4
|
+
import { Form } from '@availity/form';
|
|
5
|
+
import { AvOrganizationSelect } from '@availity/select';
|
|
6
|
+
import * as yup from 'yup';
|
|
7
|
+
import nativeForm from '@availity/native-form';
|
|
8
|
+
import { avOrganizationsApi, avWebQLApi } from '@availity/api-axios';
|
|
9
|
+
import '@availity/yup';
|
|
10
|
+
|
|
11
|
+
const SUPPORT_PERMISSION_ID = '7205';
|
|
12
|
+
|
|
13
|
+
const ORG_VALIDATION_MESSAGE = 'Please select an organization from the dropdown.';
|
|
14
|
+
|
|
15
|
+
const getToken = () => document.cookie.replace(/(?:(?:^|.*;\s*)XSRF-TOKEN\s*=\s*([^;]*).*$)|^.*$/, '$1');
|
|
16
|
+
|
|
17
|
+
const openSupport = async (values, setBlocking, setSupportIsActive, feedbackToggle) => {
|
|
18
|
+
setBlocking(true);
|
|
19
|
+
const orgsResp = await avOrganizationsApi.getOrganizations(SUPPORT_PERMISSION_ID);
|
|
20
|
+
let salesforceResponse;
|
|
21
|
+
if (orgsResp.data.organizations.some((org) => org.id === values.organization.id)) {
|
|
22
|
+
salesforceResponse = await avWebQLApi.create({
|
|
23
|
+
query: `
|
|
24
|
+
query salesforceUser($organizationId: ID!) {
|
|
25
|
+
salesforceUser(organizationId: $organizationId) {
|
|
26
|
+
id
|
|
27
|
+
accountId
|
|
28
|
+
newCommunity
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
`,
|
|
32
|
+
variables: {
|
|
33
|
+
organizationId: values.organization.id,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let href = 'mailto:support@availity.com';
|
|
39
|
+
const target = '_top';
|
|
40
|
+
|
|
41
|
+
if (!salesforceResponse || !salesforceResponse.data) {
|
|
42
|
+
window.open(href, target);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const salesforceData = salesforceResponse.data.data.salesforceUser;
|
|
46
|
+
const ssoId = 'BJx3fqX3BH';
|
|
47
|
+
const newCommutySsoId = 'E-t_s_JQ0P';
|
|
48
|
+
|
|
49
|
+
if (salesforceData) {
|
|
50
|
+
const attributes = {
|
|
51
|
+
orgGenKey: values.organization.customerId,
|
|
52
|
+
organizationName: values.organization.name,
|
|
53
|
+
partyId: values.organization.id,
|
|
54
|
+
payerId: 'AVAILITY',
|
|
55
|
+
X_Client_ID: '5430d59f-c5cc-4be7-be5a-34472ec30fe9',
|
|
56
|
+
X_XSRF_TOKEN: getToken(),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if (salesforceData.id && salesforceData.accountId) {
|
|
60
|
+
attributes.avSalesForceContactId = salesforceData.id;
|
|
61
|
+
attributes.avSalesForceContactAccId = salesforceData.accountId;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
href = `/ms/api/availity/internal/spc/magneto/sso/v1/saml/${salesforceData.newCommunity ? newCommutySsoId : ssoId}`;
|
|
65
|
+
|
|
66
|
+
await nativeForm(ssoId, attributes, { action: href, target: '_blank' }, 'saml');
|
|
67
|
+
setBlocking(false);
|
|
68
|
+
} else {
|
|
69
|
+
window.open(href, target);
|
|
70
|
+
}
|
|
71
|
+
setSupportIsActive(false);
|
|
72
|
+
feedbackToggle(false);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function orgSelectTest(msg) {
|
|
76
|
+
const orgSelectSchema = yup.object().shape({
|
|
77
|
+
id: yup.string().isRequired(true, ORG_VALIDATION_MESSAGE),
|
|
78
|
+
name: yup.string().isRequired(true, ORG_VALIDATION_MESSAGE),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return this.test({
|
|
82
|
+
name: 'orgSelectTest',
|
|
83
|
+
exclusive: true,
|
|
84
|
+
message: msg || 'This field is invalid.',
|
|
85
|
+
async test(componentValue) {
|
|
86
|
+
if (!componentValue) return true;
|
|
87
|
+
|
|
88
|
+
const valid = await orgSelectSchema.isValid(componentValue);
|
|
89
|
+
return valid;
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
yup.addMethod(yup.object, 'orgSelectTest', orgSelectTest);
|
|
95
|
+
|
|
96
|
+
const SupportForm = ({ setSupportIsActive, setBlocking, feedbackToggle }) => (
|
|
97
|
+
<>
|
|
98
|
+
<ModalHeader id="support-form-header" role="heading" aria-level="2" className="h5" tag="div">
|
|
99
|
+
Open Support Ticket
|
|
100
|
+
</ModalHeader>
|
|
101
|
+
<Form
|
|
102
|
+
aria-label="Support Form"
|
|
103
|
+
aria-describedby="support-form-header"
|
|
104
|
+
role="form"
|
|
105
|
+
data-testid="support-form"
|
|
106
|
+
initialValues={{
|
|
107
|
+
organization: undefined,
|
|
108
|
+
}}
|
|
109
|
+
validationSchema={yup.object({
|
|
110
|
+
organization: yup
|
|
111
|
+
.object()
|
|
112
|
+
.orgSelectTest(ORG_VALIDATION_MESSAGE)
|
|
113
|
+
.isRequired(ORG_VALIDATION_MESSAGE)
|
|
114
|
+
.typeError(ORG_VALIDATION_MESSAGE),
|
|
115
|
+
})}
|
|
116
|
+
onSubmit={(values) => openSupport(values, setBlocking, setSupportIsActive, feedbackToggle)}
|
|
117
|
+
>
|
|
118
|
+
<ModalBody>
|
|
119
|
+
<AvOrganizationSelect
|
|
120
|
+
name="organization"
|
|
121
|
+
label="Select an Organization"
|
|
122
|
+
data-testid="org-dropdown"
|
|
123
|
+
getResult={(data) => data.organizations}
|
|
124
|
+
/>
|
|
125
|
+
</ModalBody>
|
|
126
|
+
<ModalFooter>
|
|
127
|
+
<Button
|
|
128
|
+
onClick={() => setSupportIsActive(false)}
|
|
129
|
+
color="secondary"
|
|
130
|
+
type="button"
|
|
131
|
+
onKeyDown={({ keyCode }) => keyCode === 13 && setSupportIsActive(false)}
|
|
132
|
+
>
|
|
133
|
+
Close
|
|
134
|
+
</Button>
|
|
135
|
+
<Button data-testid="submit-btn" type="submit" color="primary">
|
|
136
|
+
Continue
|
|
137
|
+
</Button>
|
|
138
|
+
</ModalFooter>
|
|
139
|
+
</Form>
|
|
140
|
+
</>
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
SupportForm.propTypes = {
|
|
144
|
+
setSupportIsActive: PropTypes.func,
|
|
145
|
+
setBlocking: PropTypes.func,
|
|
146
|
+
feedbackToggle: PropTypes.func,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export default SupportForm;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Modal } from 'reactstrap';
|
|
4
|
+
import BlockUi from '@availity/block-ui';
|
|
5
|
+
|
|
6
|
+
import SupportForm from './SupportForm';
|
|
7
|
+
|
|
8
|
+
const SupportModal = ({ supportIsActive, toggle, supportZIndex, setSupportIsActive, feedbackToggle }) => {
|
|
9
|
+
const [blocking, setBlocking] = useState(false);
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<Modal
|
|
13
|
+
fade
|
|
14
|
+
id="supportModal"
|
|
15
|
+
data-testid="supportModal"
|
|
16
|
+
aria-labelledby="support-form-header"
|
|
17
|
+
isOpen={supportIsActive}
|
|
18
|
+
toggle={toggle}
|
|
19
|
+
zIndex={supportZIndex}
|
|
20
|
+
>
|
|
21
|
+
<BlockUi tag="div" blocking={blocking}>
|
|
22
|
+
<SupportForm
|
|
23
|
+
setSupportIsActive={setSupportIsActive}
|
|
24
|
+
setBlocking={setBlocking}
|
|
25
|
+
feedbackToggle={feedbackToggle}
|
|
26
|
+
/>
|
|
27
|
+
</BlockUi>
|
|
28
|
+
</Modal>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
SupportModal.propTypes = {
|
|
33
|
+
supportIsActive: PropTypes.bool.isRequired,
|
|
34
|
+
toggle: PropTypes.func.isRequired,
|
|
35
|
+
supportZIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
|
36
|
+
setSupportIsActive: PropTypes.func,
|
|
37
|
+
feedbackToggle: PropTypes.func,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default SupportModal;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../../dist/out-tsc",
|
|
5
|
+
"module": "commonjs",
|
|
6
|
+
"types": ["jest", "node", "@testing-library/jest-dom"],
|
|
7
|
+
"allowJs": true
|
|
8
|
+
},
|
|
9
|
+
"include": ["**/*.test.js", "**/*.test.ts", "**/*.test.tsx", "**/*.d.ts"]
|
|
10
|
+
}
|