resources/web/docs_js/components/feedback_modal.jsx (223 lines of code) (raw):
import { h, Component } from '../../../../../../node_modules/preact';
const FEEDBACK_URL = 'https://docs.elastic.co/api/feedback'
const MAX_COMMENT_LENGTH = 1000;
export default class FeedbackModal extends Component {
constructor(props) {
super(props);
this.state = {
comment: '',
modalClosed: false,
isLoading: false,
hasError: false,
};
this.onEscape = this.onEscape.bind(this);
this.resetState = this.resetState.bind(this);
this.submitFeedback = this.submitFeedback.bind(this);
}
onEscape(event) {
if (event.key === 'Escape') {
this.resetState();
}
}
resetState() {
this.setState({ modalClosed: true });
document.querySelectorAll('.isPressed').forEach((el) => {
el.classList.remove('isPressed');
});
}
submitFeedback() {
this.setState({ isLoading: true });
fetch(FEEDBACK_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
comment: this.state.comment,
feedback: this.props.isLiked ? 'liked' : 'disliked',
url: window.location.href,
}),
})
.then((response) => response.json())
.then(() => {
this.setState({ modalClosed: true })
document.getElementById('feedbackSuccess').classList.remove('hidden')
document.querySelectorAll('.feedbackButton').forEach((el) => {
el.disabled = true
})
})
.catch((error) => {
this.setState({ isLoading: false, hasError: true });
console.error('Error:', error);
});
}
componentDidMount() {
document.addEventListener('keydown', this.onEscape, false);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.onEscape, false);
}
render(props, state) {
const { isLiked } = props;
const { modalClosed, isLoading, hasError, comment } = state;
const maxCommentLengthReached = comment.length > MAX_COMMENT_LENGTH;
const sendDisabled = isLoading || maxCommentLengthReached;
if (modalClosed) {
return null;
}
return (
<div
data-relative-to-header="above"
id="feedbackModal"
>
<div
data-focus-guard="true"
tabindex="0"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
></div>
<div data-focus-lock-disabled="false">
<div className="feedbackModalContent" tabindex="0">
<button
className="closeIcon"
type="button"
aria-label="Closes this modal window"
onClick={this.resetState}
disabled={isLoading}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
role="img"
data-icon-type="cross"
data-is-loaded="true"
aria-hidden="true"
>
<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>
</svg>
</button>
<div className="feedbackModalHeader">
<h2>Send us your feedback</h2>
</div>
<div className="feedbackModalBody">
<div className="feedbackModalBodyOverflow">
<div>
Thank you for helping us improve Elastic documentation.
</div>
<div className="spacer"></div>
<div className="feedbackForm">
<div className="feedbackFormRow">
<div className="feedbackFormRow__labelWrapper">
<label
className="feedbackFormLabel"
id="feedbackLabel"
for="feedbackComment"
>
Additional comment (optional)
</label>
</div>
<div className="feedbackFormRow__fieldWrapper">
<div className="feedbackFormControlLayout">
<div className="feedbackFormControlLayout__childrenWrapper">
<textarea
className="feedbackTextArea"
rows="6"
id="feedbackComment"
disabled={isLoading}
onKeyUp={(e) =>
this.setState({ comment: e.target.value })
}
></textarea>
{maxCommentLengthReached && (
<div className="feedbackFormError">
Max comment length of {MAX_COMMENT_LENGTH}{' '}
characters reached.
<br />
<br />
Character count: {comment.length}
</div>
)}
{hasError && (
<div className="feedbackFormError">
There was a problem submitting your feedback.
<br />
<br />
Please try again.
</div>
)}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
className={`feedbackModalFooter ${isLoading ? 'loading' : ''}`}
>
<button
className="feedbackButton cancelButton"
type="button"
onClick={this.resetState}
disabled={isLoading}
>
<span className="feedbackButtonContent">
<span>Cancel</span>
</span>
</button>
<button
type="button"
disabled={sendDisabled}
className={`feedbackButton sendButton ${
isLiked ? 'like' : 'dislike'
}`}
onClick={this.submitFeedback}
>
<span className="loadingContent">
<span
class="loadingSpinner"
role="progressbar"
aria-label="Loading"
style="border-color: rgb(0, 119, 204) currentcolor currentcolor;"
></span>
<span>Sending...</span>
</span>
<span className="feedbackButtonContent">
<span>Send</span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
className="sendIcon like"
role="img"
aria-hidden="true"
>
<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>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
className="sendIcon dislike"
role="img"
aria-hidden="true"
>
<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>
</svg>
</span>
</button>
</div>
</div>
</div>
<div
data-focus-guard="true"
tabindex="0"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
></div>
</div>
);
}
}