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