From a27da11e7622ba99b730c1d2414e8af1883c46e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20R=C3=B6sel?= <320272+Traxmaxx@users.noreply.github.com> Date: Wed, 11 Oct 2023 14:43:46 +0200 Subject: [PATCH] Feature/tech 1656 (#1043) * feat: Add feedback widget with screenshot and file upload functionality * fix: add preview-api storybook package due to deprecations happening --- .../feedback-widget/FeedbackWidget.tsx | 475 +++++++ dashboard/components/input/Input.tsx | 16 +- .../alerts/InventoryViewAlertsEditAlert.tsx | 3 + dashboard/components/modal/Modal.stories.tsx | 42 + dashboard/components/modal/Modal.tsx | 11 +- dashboard/components/navbar/Navbar.tsx | 12 +- dashboard/package-lock.json | 1237 +++++++++-------- dashboard/package.json | 3 + dashboard/services/settingsService.ts | 13 + 9 files changed, 1184 insertions(+), 628 deletions(-) create mode 100644 dashboard/components/feedback-widget/FeedbackWidget.tsx create mode 100644 dashboard/components/modal/Modal.stories.tsx diff --git a/dashboard/components/feedback-widget/FeedbackWidget.tsx b/dashboard/components/feedback-widget/FeedbackWidget.tsx new file mode 100644 index 000000000..51bf836d4 --- /dev/null +++ b/dashboard/components/feedback-widget/FeedbackWidget.tsx @@ -0,0 +1,475 @@ +import { useState, useRef, useCallback, memo, SyntheticEvent } from 'react'; +import { FileUploader } from 'react-drag-drop-files'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { toBlob } from 'html-to-image'; +import Image from 'next/image'; + +import Modal from '@components/modal/Modal'; +import Input from '@components/input/Input'; +import settingsService from '@services/settingsService'; +import Button from '@components/button/Button'; +import useToast from '@components/toast/hooks/useToast'; +import Toast from '@components/toast/Toast'; + +// We define the placeholder here for convenience +// It's difficult to read when passed inline +const textAreaPlaceholder = `Example: +Steps to Reproduce +1. Describe the actions you took leading to the bug. +2. Include any specific settings or options you selected. + +Expected Behavior +1. Explain what you expected to happen. +2. Detail how the feature or function should work. + +Outcome +1. Describe what actually happened. +2. Include any error messages or unexpected behavior.`; + +const useFeedbackWidget = (defaultState: boolean = false) => { + const [showFeedbackModel, setShowFeedbackModal] = useState(defaultState); + + const FILE_TYPES = ['JPG', 'PNG', 'GIF', 'TXT', 'LOG', 'MP4', 'AVI', 'MOV']; + const FEEDBACK_MODAL_ID = 'feedback-modal'; + const MAX_FILE_SIZE_MB = 37; + + function openFeedbackModal() { + setShowFeedbackModal(true); + } + + function closeFeedbackModal() { + setShowFeedbackModal(false); + } + + function toggleFeedbackModal() { + setShowFeedbackModal(!showFeedbackModel); + } + + const screenshotModalFilter = (node: HTMLElement) => + !node.id?.startsWith(FEEDBACK_MODAL_ID); + + const FeedbackModal = () => { + const [email, updateEmail] = useState(''); + const [description, updateDescription] = useState(''); + const [isTakingScreenCapture, setIsTakingScreenCapture] = useState(false); + const [fileAttachement, setFileAttachement] = useState(null); + const [isSendingFeedback, setIsSendingFeedback] = useState(false); + const { toast, setToast, dismissToast } = useToast(); + + async function takeScreenshot() { + if ( + document.documentElement === null || + isSendingFeedback || + isTakingScreenCapture || + fileAttachement !== null + ) { + return; + } + setIsTakingScreenCapture(true); + + toBlob(document.documentElement, { + cacheBust: true, + filter: screenshotModalFilter + }) + .then(async blob => { + // setScreenshotBlob(blob); + if (blob !== null) { + const screenShotFile = new File( + [blob], + 'Automated screen capture', + { + type: blob.type + } + ); + + setFileAttachement(screenShotFile); + } + + setToast({ + hasError: false, + title: 'Screen capture', + message: + 'A screenshot of your current page on Komiser has been captured and attached to your feedback.' + }); + }) + .catch(err => { + setToast({ + hasError: true, + title: 'Screen capture failed', + message: + 'The capture of your current page on Komiser couldn’t be saved. Please try again or upload a screenshot manually. Our support is also happy to help you!' + }); + }) + .finally(() => { + setIsTakingScreenCapture(false); + }); + } + + function clearFeedbackForm() { + setFileAttachement(null); + updateDescription(''); + updateEmail(''); + } + + async function uploadFeedback(e: SyntheticEvent) { + if (!isSendingFeedback) { + try { + setIsSendingFeedback(true); + e.preventDefault(); + const formData = new FormData(); + + formData.append('description', description); + if (email) formData.append('email', email); + if (fileAttachement && fileAttachement !== null) + formData.append('image', fileAttachement); + + settingsService + .sendFeedback(formData) + .then(result => { + setToast({ + hasError: false, + title: 'Feedback sent', + message: + result.Response || + 'Your insights are valuable to us. Thank you for helping us improve!' + }); + clearFeedbackForm(); + }) + .catch(error => { + setToast({ + hasError: true, + title: 'Feedback', + message: 'An Error happened. Maybe try again please!' + }); + }) + .finally(() => { + setIsSendingFeedback(false); + }); + } catch { + setIsSendingFeedback(false); + } + } + } + + function uploadFile(attachement: File) { + setFileAttachement(attachement); + } + + return ( + <> + closeFeedbackModal()} + id={FEEDBACK_MODAL_ID} + > +
+

+ Describe your issue +

+

+ By providing details of the issue you’ve encountered and outlining + the steps to reproduce it, we’ll be able to give you better + support. +

+
+ { + updateEmail(change.email); + }} + value={email} + required + /> +