Skip to content

Commit b4bfd92

Browse files
authored
Merge pull request #5193 from raft-tech/5072-implement-contextual-feedback-modal-on-data-file-submission
#5072 Implement contextual feedback modal on data file submission
2 parents a8e34db + 30a02db commit b4bfd92

29 files changed

+1927
-708
lines changed

tdrs-frontend/src/App.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Routes from './components/Routes'
44
import { Alert } from './components/Alert'
55
import Header from './components/Header'
66
import Footer from './components/Footer'
7-
import Feedback from './components/FeedbackModal/Feedback'
7+
import Feedback from './components/Feedback/Feedback'
88
import { useSelector } from 'react-redux'
99
import { useRUM } from './hooks/useRUM'
1010

tdrs-frontend/src/App.test.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { mount } from 'enzyme'
44
import GovBanner from './components/GovBanner'
55
import Header from './components/Header'
66
import { Alert } from './components/Alert'
7-
import Feedback from './components/FeedbackModal/Feedback'
7+
import Feedback from './components/Feedback/Feedback'
88
import { thunk } from 'redux-thunk'
99
import configureStore from 'redux-mock-store'
1010
import { Provider } from 'react-redux'
@@ -28,9 +28,20 @@ describe('App.js', () => {
2828
auth: {
2929
user: null,
3030
},
31+
feedbackWidget: {
32+
isOpen: false,
33+
lockedDataType: null,
34+
},
3135
}
3236
const mockStore = configureStore([thunk])
3337

38+
beforeEach(() => {
39+
Object.defineProperty(window, 'location', {
40+
writable: true,
41+
value: { pathname: '/' },
42+
})
43+
})
44+
3445
afterEach(() => {
3546
window.location.href = ''
3647
})

tdrs-frontend/src/__mocks__/mockFeedbackAxiosApi.js

Lines changed: 0 additions & 28 deletions
This file was deleted.

tdrs-frontend/src/assets/feedback/Feedback.scss

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
.feedback-modal-overlay {
55
outline: none;
6+
z-index: 9999; // Ensure modal overlay is above all other content
67

78
&:focus,
89
&:focus-visible {
@@ -14,7 +15,7 @@
1415
position: fixed;
1516
bottom: 0;
1617
right: 40px;
17-
z-index: 1000; // default when modal is closed
18+
z-index: 1100; // default when modal is closed
1819
}
1920

2021
.feedback-button.modal-open {
@@ -44,6 +45,83 @@
4445
}
4546
}
4647

48+
.feedback-widget {
49+
right: 14rem;
50+
width: 300px;
51+
max-height: 90vh;
52+
overflow-y: auto;
53+
background: white;
54+
border: 1px solid #ccc;
55+
border-radius: 0.5rem;
56+
padding: 1rem;
57+
padding-bottom: 0;
58+
z-index: 1000; // default when modal is closed
59+
transition: bottom 0.2s ease-in-out;
60+
61+
&:focus,
62+
&:focus-visible {
63+
outline: none !important;
64+
}
65+
}
66+
67+
@media (max-width: 480px) {
68+
.feedback-widget {
69+
right: 0.5rem;
70+
left: 0.5rem;
71+
width: auto;
72+
max-width: 100%;
73+
}
74+
}
75+
76+
.feedback-widget-header {
77+
display: flex;
78+
justify-content: space-between;
79+
align-items: center;
80+
margin-bottom: -0.8rem;
81+
}
82+
83+
.feedback-widget-thank-you-header {
84+
display: flex;
85+
justify-content: space-between;
86+
align-items: center;
87+
z-index: 1000;
88+
}
89+
90+
.spinner {
91+
width: 18px;
92+
height: 18px;
93+
border: 2px solid rgba(0, 0, 0, 0.2);
94+
border-top: 2px solid #000;
95+
border-radius: 50%;
96+
animation: spin 1s linear infinite;
97+
}
98+
99+
@keyframes spin {
100+
to {
101+
transform: rotate(360deg);
102+
}
103+
}
104+
105+
.usa-checkbox__label.feedback-widget-anonymous-label {
106+
margin-top:0.5rem;
107+
padding-left: 1.65rem;
108+
font-size: 0.95rem;
109+
}
110+
111+
.usa-checkbox__label.feedback-widget-anonymous-label::before {
112+
height:1rem;
113+
width:1rem;
114+
}
115+
116+
.usa-textarea.feedback-widget-textarea {
117+
height: 5rem;
118+
width: calc(100% - 1rem);
119+
margin-left: 0.5rem;
120+
margin-right: 0.5rem;
121+
font-size: 0.85rem;
122+
padding: 0.4rem;
123+
}
124+
47125
.feedback-group {
48126
width: 37.5rem;
49127
height: 9.375rem;
@@ -56,6 +134,35 @@
56134
border-color: #b50909;
57135
background-color: #fefafa;
58136
}
137+
138+
&.no-background {
139+
border: none;
140+
background-color: transparent;
141+
height: auto;
142+
width: auto;
143+
}
144+
145+
.widget-size {
146+
width: 100%;
147+
height: auto;
148+
background: none;
149+
border: none;
150+
padding: 0.5rem;
151+
152+
.rating-option {
153+
width: 42px;
154+
155+
label.radio-icon {
156+
width: 100%;
157+
padding: 3px;
158+
}
159+
}
160+
161+
.feedback-label {
162+
font-size: 0.9rem;
163+
padding-top: 10px;
164+
}
165+
}
59166
}
60167

61168
.feedback-label {
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import React, { useState } from 'react'
2+
import { useSelector, useDispatch } from 'react-redux'
3+
import Button from '../Button'
4+
import FeedbackModal from './FeedbackModal'
5+
import FeedbackForm from './FeedbackForm'
6+
import FeedbackWidget from './FeedbackWidget'
7+
import classNames from 'classnames'
8+
import {
9+
FEEDBACK_MODAL_HEADER,
10+
FEEDBACK_SUCCESS_RESPONSE_HEADER,
11+
TANF_SUPPORT_EMAIL,
12+
} from './FeedbackConstants'
13+
import { closeFeedbackWidget } from '../../reducers/feedbackWidget'
14+
15+
function Feedback() {
16+
// Redux state for widget (used on /reports)
17+
const dispatch = useDispatch()
18+
const isWidgetOpen = useSelector((state) => state.feedbackWidget.isOpen)
19+
const lockedDataType = useSelector(
20+
(state) => state.feedbackWidget.lockedDataType
21+
)
22+
23+
// Feedback Modal local state
24+
const [isModalOpen, setIsModalOpen] = useState(false)
25+
const [isFeedbackSubmitted, setIsFeedbackSubmitted] = useState(false)
26+
27+
const showWidget =
28+
(window.location.pathname || '').startsWith('/data-files') ||
29+
(window.location.pathname || '').startsWith('/fra-data-files')
30+
31+
const handleOpenModal = () => {
32+
setIsModalOpen(true)
33+
}
34+
35+
const handleCloseModal = () => {
36+
setIsModalOpen(false)
37+
setIsFeedbackSubmitted(false)
38+
}
39+
40+
const handleWidgetClose = () => {
41+
dispatch(closeFeedbackWidget())
42+
}
43+
44+
const handleOnFeedbackSubmit = () => {
45+
setIsFeedbackSubmitted(true)
46+
}
47+
48+
return (
49+
<>
50+
{/* Show the floating widget if it's open */}
51+
{isWidgetOpen && showWidget ? (
52+
<div className="feedback-button">
53+
<FeedbackWidget
54+
isOpen={isWidgetOpen}
55+
onClose={handleWidgetClose}
56+
dataType={lockedDataType}
57+
/>
58+
</div>
59+
) : (
60+
<>
61+
<Button
62+
type="button"
63+
data-testid="usa-feedback-sticky-button"
64+
className={classNames('usa-button', 'feedback-button', {
65+
'modal-open': isModalOpen,
66+
})}
67+
onClick={handleOpenModal}
68+
>
69+
Give Feedback
70+
</Button>
71+
{isModalOpen && !isFeedbackSubmitted ? (
72+
<FeedbackModal
73+
id="feedback-modal"
74+
title="Tell us how we can improve TDP"
75+
isOpen={isModalOpen}
76+
message={FEEDBACK_MODAL_HEADER}
77+
onClose={handleCloseModal}
78+
>
79+
<div
80+
style={{
81+
justifyContent: 'center',
82+
alignItems: 'center',
83+
marginTop: '5px',
84+
}}
85+
>
86+
<FeedbackForm
87+
isGeneralFeedback={true}
88+
onFeedbackSubmit={handleOnFeedbackSubmit}
89+
/>
90+
</div>
91+
</FeedbackModal>
92+
) : (
93+
isModalOpen && (
94+
<FeedbackModal
95+
id="feedback-thank-you-modal"
96+
title={FEEDBACK_SUCCESS_RESPONSE_HEADER}
97+
isOpen={isModalOpen}
98+
message={
99+
<p>
100+
Your response has been recorded. If you're encountering an
101+
issue you need support to resolve, please feel free to email
102+
us at{' '}
103+
<a
104+
className="usa-link"
105+
href={`mailto:${TANF_SUPPORT_EMAIL}`}
106+
>
107+
{TANF_SUPPORT_EMAIL}
108+
</a>
109+
.
110+
</p>
111+
}
112+
onClose={handleCloseModal}
113+
>
114+
<div className="margin-x-4 margin-bottom-4">
115+
<button
116+
id="feedback-close-button"
117+
data-testid="feedback-submit-close-button"
118+
className="usa-button"
119+
type="button"
120+
onClick={handleCloseModal}
121+
style={{ marginTop: '8px' }}
122+
onKeyDown={(e) => {
123+
if (e.key === 'Enter') {
124+
e.preventDefault()
125+
handleCloseModal()
126+
}
127+
}}
128+
>
129+
Close
130+
</button>
131+
</div>
132+
</FeedbackModal>
133+
)
134+
)}
135+
</>
136+
)}
137+
</>
138+
)
139+
}
140+
141+
export default Feedback

0 commit comments

Comments
 (0)