Skip to content

Commit 4097c83

Browse files
Add feedback modal to end of body
1 parent e238642 commit 4097c83

File tree

4 files changed

+631
-8
lines changed

4 files changed

+631
-8
lines changed
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
import { h, Component } from '../../../../../../node_modules/preact';
2+
3+
const FEEDBACK_URL = 'https://docs.elastic.co/api/feedback'
4+
const MAX_COMMENT_LENGTH = 1000;
5+
6+
export default class FeedbackModal extends Component {
7+
constructor(props) {
8+
super(props);
9+
this.state = {
10+
comment: '',
11+
modalClosed: false,
12+
isLoading: false,
13+
hasError: false,
14+
};
15+
this.onEscape = this.onEscape.bind(this);
16+
this.resetState = this.resetState.bind(this);
17+
this.submitFeedback = this.submitFeedback.bind(this);
18+
}
19+
20+
onEscape(event) {
21+
if (event.key === 'Escape') {
22+
this.resetState();
23+
}
24+
}
25+
26+
resetState() {
27+
this.setState({ modalClosed: true });
28+
document.querySelectorAll('.isPressed').forEach((el) => {
29+
el.classList.remove('isPressed');
30+
});
31+
}
32+
33+
submitFeedback() {
34+
this.setState({ isLoading: true });
35+
fetch(FEEDBACK_URL, {
36+
method: 'POST',
37+
headers: {
38+
'Content-Type': 'application/json',
39+
},
40+
body: JSON.stringify({
41+
comment: this.state.comment,
42+
feedback: this.props.isLiked ? 'liked' : 'disliked',
43+
}),
44+
})
45+
.then((response) => response.json())
46+
.then(() => {
47+
this.setState({ modalClosed: true })
48+
document.getElementById('feedbackSuccess').classList.remove('hidden')
49+
document.querySelectorAll('.feedbackButton').forEach((el) => {
50+
el.disabled = true
51+
})
52+
})
53+
.catch((error) => {
54+
this.setState({ isLoading: false, hasError: true });
55+
console.error('Error:', error);
56+
});
57+
58+
}
59+
60+
componentDidMount() {
61+
document.addEventListener('keydown', this.onEscape, false);
62+
}
63+
64+
componentWillUnmount() {
65+
document.removeEventListener('keydown', this.onEscape, false);
66+
}
67+
68+
render(props, state) {
69+
const { isLiked } = props;
70+
const { modalClosed, isLoading, hasError, comment } = state;
71+
const maxCommentLengthReached = comment.length > MAX_COMMENT_LENGTH;
72+
const sendDisabled = isLoading || maxCommentLengthReached;
73+
74+
if (modalClosed) {
75+
return null;
76+
}
77+
78+
return (
79+
<div
80+
data-relative-to-header="above"
81+
id="feedbackModal"
82+
>
83+
<div
84+
data-focus-guard="true"
85+
tabindex="0"
86+
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
87+
></div>
88+
<div data-focus-lock-disabled="false">
89+
<div className="feedbackModalContent" tabindex="0">
90+
<button
91+
className="closeIcon"
92+
type="button"
93+
aria-label="Closes this modal window"
94+
onClick={this.resetState}
95+
disabled={isLoading}
96+
>
97+
<svg
98+
xmlns="http://www.w3.org/2000/svg"
99+
width="16"
100+
height="16"
101+
viewBox="0 0 16 16"
102+
role="img"
103+
data-icon-type="cross"
104+
data-is-loaded="true"
105+
aria-hidden="true"
106+
>
107+
<path d="M7.293 8 3.146 3.854a.5.5 0 1 1 .708-.708L8 7.293l4.146-4.147a.5.5 0 0 1 .708.708L8.707 8l4.147 4.146a.5.5 0 0 1-.708.708L8 8.707l-4.146 4.147a.5.5 0 0 1-.708-.708L7.293 8Z"></path>
108+
</svg>
109+
</button>
110+
<div className="feedbackModalHeader">
111+
<h2>Send us your feedback</h2>
112+
</div>
113+
<div className="feedbackModalBody">
114+
<div className="feedbackModalBodyOverflow">
115+
<div>
116+
Thank you for helping us improve Elastic documentation.
117+
</div>
118+
<div className="spacer"></div>
119+
<div className="feedbackForm">
120+
<div className="feedbackFormRow">
121+
<div className="feedbackFormRow__labelWrapper">
122+
<label
123+
className="feedbackFormLabel"
124+
id="feedbackLabel"
125+
for="feedbackComment"
126+
>
127+
Additional comment (optional)
128+
</label>
129+
</div>
130+
<div className="feedbackFormRow__fieldWrapper">
131+
<div className="feedbackFormControlLayout">
132+
<div className="feedbackFormControlLayout__childrenWrapper">
133+
<textarea
134+
className="feedbackTextArea"
135+
rows="6"
136+
id="feedbackComment"
137+
disabled={isLoading}
138+
onKeyUp={(e) =>
139+
this.setState({ comment: e.target.value })
140+
}
141+
></textarea>
142+
{maxCommentLengthReached && (
143+
<div className="feedbackFormError">
144+
Max comment length of {MAX_COMMENT_LENGTH}{' '}
145+
characters reached.
146+
<br />
147+
<br />
148+
Character count: {comment.length}
149+
</div>
150+
)}
151+
{hasError && (
152+
<div className="feedbackFormError">
153+
There was a problem submitting your feedback.
154+
<br />
155+
<br />
156+
Please try again.
157+
</div>
158+
)}
159+
</div>
160+
</div>
161+
</div>
162+
</div>
163+
</div>
164+
</div>
165+
</div>
166+
<div
167+
className={`feedbackModalFooter ${isLoading ? 'loading' : ''}`}
168+
>
169+
<button
170+
className="feedbackButton cancelButton"
171+
type="button"
172+
onClick={this.resetState}
173+
disabled={isLoading}
174+
>
175+
<span className="feedbackButtonContent">
176+
<span>Cancel</span>
177+
</span>
178+
</button>
179+
<button
180+
type="button"
181+
disabled={sendDisabled}
182+
className={`feedbackButton sendButton ${
183+
isLiked ? 'like' : 'dislike'
184+
}`}
185+
onClick={this.submitFeedback}
186+
>
187+
<span className="loadingContent">
188+
<span
189+
class="loadingSpinner"
190+
role="progressbar"
191+
aria-label="Loading"
192+
style="border-color: rgb(0, 119, 204) currentcolor currentcolor;"
193+
></span>
194+
<span>Sending...</span>
195+
</span>
196+
<span className="feedbackButtonContent">
197+
<span>Send</span>
198+
<svg
199+
xmlns="http://www.w3.org/2000/svg"
200+
width="24"
201+
height="24"
202+
viewBox="0 0 24 24"
203+
className="sendIcon like"
204+
role="img"
205+
aria-hidden="true"
206+
>
207+
<path d="M9 21h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2c0-1.1-.9-2-2-2h-6.31l.95-4.57l.03-.32c0-.41-.17-.79-.44-1.06L14.17 1L7.58 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2zM9 9l4.34-4.34L12 10h9v2l-3 7H9V9zM1 9h4v12H1z"></path>
208+
</svg>
209+
<svg
210+
xmlns="http://www.w3.org/2000/svg"
211+
width="24"
212+
height="24"
213+
viewBox="0 0 24 24"
214+
className="sendIcon dislike"
215+
role="img"
216+
aria-hidden="true"
217+
>
218+
<path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57l-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm0 12l-4.34 4.34L12 14H3v-2l3-7h9v10zm4-12h4v12h-4z"></path>
219+
</svg>
220+
</span>
221+
</button>
222+
</div>
223+
</div>
224+
</div>
225+
<div
226+
data-focus-guard="true"
227+
tabindex="0"
228+
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
229+
></div>
230+
</div>
231+
);
232+
}
233+
}

resources/web/docs_js/index.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import AlternativeSwitcher from "./components/alternative_switcher";
22
import ConsoleWidget from "./components/console_widget";
3+
import FeedbackModal from './components/feedback_modal';
34
import FeedbackWidget from './components/feedback_widget';
45
import Modal from "./components/modal";
56
import mount from "./components/mount";
@@ -92,6 +93,11 @@ export function init_console_widgets() {
9293

9394
export function init_feedback_widget() {
9495
mount($('#feedbackWidgetContainer'), FeedbackWidget);
96+
$('.feedbackButton').click(function () {
97+
const isLiked = $(this).hasClass('feedbackLiked');
98+
$(this).addClass('isPressed');
99+
mount($('#feedbackModalContainer'), FeedbackModal, { isLiked: isLiked });
100+
});
95101
}
96102

97103
export function init_sense_widgets() {
@@ -337,8 +343,8 @@ $(function() {
337343
}
338344
})
339345
// Bold the item in the popover that represents
340-
// the current book
341-
const currentBookTitle = dropDownAnchor.text()
346+
// the current book
347+
const currentBookTitle = dropDownAnchor.text()
342348
const items = dropDownContent.find("li")
343349
items.each(function(i) {
344350
if (items[i].innerText === currentBookTitle) {

0 commit comments

Comments
 (0)