Path: blob/main/components/dashboard/src/feedback-form/FeedbackComponent.tsx
2500 views
/**1* Copyright (c) 2022 Gitpod GmbH. All rights reserved.2* Licensed under the GNU Affero General Public License (AGPL).3* See License.AGPL.txt in the project root for license information.4*/56import { useState } from "react";7import starry from "../images/feedback/starry-emoji.svg";8import happy from "../images/feedback/happy-emoji.svg";9import meh from "../images/feedback/meh-emoji.svg";10import crying from "../images/feedback/crying-emoji.svg";11import { trackEvent, TrackFeedback } from "../Analytics";12import { StartWorkspaceError } from "../start/StartPage";13import { Heading2 } from "../components/typography/headings";14import { Button } from "@podkit/buttons/Button";15import { cn } from "@podkit/lib/cn";1617function FeedbackComponent(props: {18onClose?: () => void;19isModal: boolean;20isError: boolean;21message?: string;22initialSize?: number;23errorObject?: StartWorkspaceError;24errorMessage?: string;25}) {26const [text, setText] = useState<string>("");27const [selectedEmoji, setSelectedEmoji] = useState<number | undefined>();28const [isFeedbackSubmitted, setIsFeedbackSubmitted] = useState<boolean>(false);2930const onClose = () => {31if (props.onClose) {32props.onClose();33}34setSelectedEmoji(undefined);35};36const onSubmit = () => {37if (selectedEmoji) {38const feedbackObj: TrackFeedback = {39score: selectedEmoji,40feedback: text,41href: window.location.href,42path: window.location.pathname,43error_object: props.errorObject || undefined,44error_message: props.errorMessage,45};46trackEvent("feedback_submitted", feedbackObj);47}4849setIsFeedbackSubmitted(true);50};5152const handleClick = (emojiScore: number) => {53setSelectedEmoji(emojiScore);54};5556const emojiGroup = (width: number) => {57const emojiList = [58{ id: 1, name: "crying", src: crying },59{ id: 2, name: "meh", src: meh },60{ id: 3, name: "happy", src: happy },61{ id: 4, name: "starry", src: starry },62];63return emojiList.map((emoji) => (64<Button variant="ghost" onClick={() => handleClick(emoji.id)}>65<img66src={emoji.src}67alt={`${emoji.name} emoji`}68width={width || "24px"}69title={emoji.name}70className={cn("hover:scale-150 transition-all", selectedEmoji !== emoji.id && "grayed")}71/>72</Button>73));74};7576const minimisedFirstView = !selectedEmoji && !isFeedbackSubmitted;77const expandedWithTextView = selectedEmoji && !isFeedbackSubmitted;7879return (80<>81{props.isModal && !isFeedbackSubmitted && <Heading2 className="mb-4">Send Feedback</Heading2>}82{minimisedFirstView && (83<div84className={85"flex flex-col justify-center px-6 py-4 border-gray-200 dark:border-gray-800 " +86(props.isError ? "mt-20 bg-pk-surface-secondary rounded-xl" : "border-t")87}88>89<p90className={91"text-center text-base mb-3 dark:text-gray-400 " +92(props.isError ? "text-gray-400" : "text-gray-500")93}94>95{props.message}96</p>9798<div className="mt-4 flex items-center justify-center w-full">99{emojiGroup(props.initialSize || 50)}100</div>101</div>102)}103{expandedWithTextView && (104<div105className={106"flex flex-col px-6 py-4 border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 " +107(props.isError ? "w-96 mt-6 bg-pk-surface-secondary rounded-xl" : "-mx-6 border-t border-b")108}109>110<div className="relative">111<textarea112style={{ height: "160px", borderRadius: "6px" }}113autoFocus114className="w-full resize-none text-pk-content-secondary focus:ring-0 focus:border-gray-400 dark:focus:border-gray-400 rounded-md border bg-pk-surface-secondary border-pk-border-base"115name="name"116value={text}117placeholder="Have more feedback?"118onChange={(e) => setText(e.target.value)}119/>120</div>121<div>122<p className="mt-2 text-pk-content-secondary">123{" "}124By submitting this form you acknowledge that you have read and understood our{" "}125<a className="gp-link" target="gitpod-privacy" href="https://www.gitpod.io/privacy/">126privacy policy127</a>128.129</p>130</div>131<div className="flex justify-between mt-6">132<div className="flex bottom-5 right-5 -space-x-3">{emojiGroup(24)}</div>133<div>134<Button variant="secondary" onClick={onClose}>135Cancel136</Button>137<Button className="ml-2" onClick={onSubmit}>138Send Feedback139</Button>140</div>141</div>142</div>143)}144{isFeedbackSubmitted && (145<div146className={147"flex flex-col px-6 py-4 border-gray-200 dark:border-gray-800 " +148(props.isError ? "mt-20 bg-pk-surface-secondary rounded-xl" : "")149}150>151<p className={"text-center text-base " + (props.isError ? "text-gray-400" : "text-gray-500")}>152Thanks for your feedback, we appreciate it.153</p>154</div>155)}156</>157);158}159160export default FeedbackComponent;161162163