Add survey page
This commit is contained in:
parent
eaf4812dbe
commit
e2a37b68e4
@ -978,8 +978,8 @@ export default function ControlPanel(props) {
|
|||||||
"Report a bug": {
|
"Report a bug": {
|
||||||
function: () => window.open("/bug_report", "_blank"),
|
function: () => window.open("/bug_report", "_blank"),
|
||||||
},
|
},
|
||||||
"Suggest a feature": {
|
"Give feedback": {
|
||||||
function: () => {},
|
function: () => window.open("/survey", "_blank"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -6,7 +6,7 @@ import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary";
|
|||||||
import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
|
import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
|
||||||
import { ListPlugin } from "@lexical/react/LexicalListPlugin";
|
import { ListPlugin } from "@lexical/react/LexicalListPlugin";
|
||||||
import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
|
import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
|
||||||
import { ClearEditorPlugin } from '@lexical/react/LexicalClearEditorPlugin';
|
import { ClearEditorPlugin } from "@lexical/react/LexicalClearEditorPlugin";
|
||||||
import { TRANSFORMERS } from "@lexical/markdown";
|
import { TRANSFORMERS } from "@lexical/markdown";
|
||||||
|
|
||||||
import ToolbarPlugin from "../plugins/ToolbarPlugin";
|
import ToolbarPlugin from "../plugins/ToolbarPlugin";
|
||||||
@ -15,18 +15,18 @@ import CodeHighlightPlugin from "../plugins/CodeHighlightPlugin";
|
|||||||
import AutoLinkPlugin from "../plugins/AutoLinkPlugin";
|
import AutoLinkPlugin from "../plugins/AutoLinkPlugin";
|
||||||
import "../styles/richeditor.css";
|
import "../styles/richeditor.css";
|
||||||
|
|
||||||
function Placeholder() {
|
function Placeholder({ text }) {
|
||||||
return <div className="editor-placeholder">Describe the bug</div>;
|
return <div className="editor-placeholder">{text || ""}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RichEditor({ theme }) {
|
export default function RichEditor({ theme, placeholder }) {
|
||||||
return (
|
return (
|
||||||
<div className="editor-container">
|
<div className="editor-container">
|
||||||
<ToolbarPlugin theme={theme} />
|
<ToolbarPlugin theme={theme} />
|
||||||
<div className="editor-inner">
|
<div className="editor-inner">
|
||||||
<RichTextPlugin
|
<RichTextPlugin
|
||||||
contentEditable={<ContentEditable className="editor-input" />}
|
contentEditable={<ContentEditable className="editor-input" />}
|
||||||
placeholder={<Placeholder />}
|
placeholder={<Placeholder text={placeholder} />}
|
||||||
ErrorBoundary={LexicalErrorBoundary}
|
ErrorBoundary={LexicalErrorBoundary}
|
||||||
/>
|
/>
|
||||||
<HistoryPlugin />
|
<HistoryPlugin />
|
||||||
@ -37,7 +37,7 @@ export default function RichEditor({ theme }) {
|
|||||||
<AutoLinkPlugin />
|
<AutoLinkPlugin />
|
||||||
<ListMaxIndentLevelPlugin maxDepth={7} />
|
<ListMaxIndentLevelPlugin maxDepth={7} />
|
||||||
<MarkdownShortcutPlugin transformers={TRANSFORMERS} />
|
<MarkdownShortcutPlugin transformers={TRANSFORMERS} />
|
||||||
<ClearEditorPlugin/>
|
<ClearEditorPlugin />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -93,7 +93,7 @@ function Form({ theme }) {
|
|||||||
value={data.title}
|
value={data.title}
|
||||||
onChange={(v) => setData((prev) => ({ ...prev, title: v }))}
|
onChange={(v) => setData((prev) => ({ ...prev, title: v }))}
|
||||||
/>
|
/>
|
||||||
<RichEditor theme={theme} />
|
<RichEditor theme={theme} placeholder={"Describe the bug"} />
|
||||||
<Upload
|
<Upload
|
||||||
action="#"
|
action="#"
|
||||||
ref={uploadRef}
|
ref={uploadRef}
|
||||||
|
@ -42,7 +42,7 @@ export default function SignUp() {
|
|||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-5 h-screen select-none">
|
<div className="grid grid-cols-5 h-screen select-none">
|
||||||
<div className="bg-indigo-300 col-span-3 sm:hidden md:hidden lg:col-span-2 overflow-y-hidden"></div>
|
<div className="bg-indigo-300 col-span-3 sm:hidden md:hidden lg:col-span-2 overflow-y-hidden"></div>
|
||||||
<div className="col-span-2 lg:col-span-3 sm:col-span-full md:col-span-full flex flex-col justify-center items-center my-6 mx-2">
|
<div className="col-span-2 lg:col-span-3 sm:col-span-full md:col-span-full flex flex-col justify-center items-center my-6">
|
||||||
<Link to="/">
|
<Link to="/">
|
||||||
<img src={logo} alt="logo" className="mx-auto h-[38px]" />
|
<img src={logo} alt="logo" className="mx-auto h-[38px]" />
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -1,5 +1,316 @@
|
|||||||
import React from "react";
|
import React, { useEffect, useState, useCallback, useMemo } from "react";
|
||||||
|
import logo_light from "../assets/logo_light_46.png";
|
||||||
|
import logo_dark from "../assets/logo_dark_46.png";
|
||||||
|
import {
|
||||||
|
Banner,
|
||||||
|
Button,
|
||||||
|
Toast,
|
||||||
|
Spin,
|
||||||
|
Slider,
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
|
Select,
|
||||||
|
TextArea,
|
||||||
|
} from "@douyinfe/semi-ui";
|
||||||
|
import { IconSun, IconMoon } from "@douyinfe/semi-icons";
|
||||||
|
import RichEditor from "../components/rich_editor";
|
||||||
|
import { LexicalComposer } from "@lexical/react/LexicalComposer";
|
||||||
|
import { editorConfig } from "../data/editor_config";
|
||||||
|
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
||||||
|
import { $generateHtmlFromNodes } from "@lexical/html";
|
||||||
|
import { CLEAR_EDITOR_COMMAND } from "lexical";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
function SurveyForm({ theme }) {
|
||||||
|
const [editor] = useLexicalComposerContext();
|
||||||
|
const questions = useMemo(
|
||||||
|
() => ({
|
||||||
|
satisfaction: "How satisfied are you with drawDB?",
|
||||||
|
ease: "How easy was it to get started with drawDB?",
|
||||||
|
wouldRecommend: "How likely are you to recommend drawDB?",
|
||||||
|
hadDifficulty:
|
||||||
|
"Did you encounter any difficulties when navigating drawDB?",
|
||||||
|
difficulty: "What were the difficulties you faced?",
|
||||||
|
triedOtherApps: "Have you tried apps like drawDB?",
|
||||||
|
comparison: "How did you find drawDB as compared to other apps?",
|
||||||
|
occupation: "What is your occupation?",
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [form, setForm] = useState({
|
||||||
|
satisfaction: 5,
|
||||||
|
ease: 5,
|
||||||
|
wouldRecommend: 5,
|
||||||
|
hadDifficulty: "",
|
||||||
|
difficulty: "",
|
||||||
|
triedOtherApps: "",
|
||||||
|
comparison: "",
|
||||||
|
occupation: "",
|
||||||
|
});
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
setForm({});
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = useCallback(
|
||||||
|
(e) => {
|
||||||
|
setLoading(true);
|
||||||
|
editor.update(() => {
|
||||||
|
const sendMail = async () => {
|
||||||
|
await axios
|
||||||
|
.post(`${process.env.REACT_APP_BACKEND_URL}/report_bug`, {
|
||||||
|
subject: `[SURVEY]: ${new Date().toDateString()}`,
|
||||||
|
message: `${Object.keys(questions).map(
|
||||||
|
(k) => `<div>${questions[k]}</div><div>${form[k]}</div>`
|
||||||
|
)}<div>Anything else?</div>${$generateHtmlFromNodes(editor)}`,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
Toast.success("Thanks for the feedback!");
|
||||||
|
editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined);
|
||||||
|
resetForm();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
Toast.error("Oops! Something went wrong.");
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
sendMail();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[editor, form, questions]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="py-5 px-8 mt-6 card-theme rounded-md">
|
||||||
|
<div className="my-3">
|
||||||
|
<div className="font-semibold ms-1 mb-2">{questions.satisfaction}</div>
|
||||||
|
<Slider
|
||||||
|
field="satisfaction"
|
||||||
|
min={0}
|
||||||
|
max={10}
|
||||||
|
value={form.satisfaction}
|
||||||
|
onChange={(v) => {
|
||||||
|
setForm((prev) => ({ ...prev, satisfaction: v }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="text-sm flex justify-between opacity-80">
|
||||||
|
<div>Not at all</div>
|
||||||
|
<div>Extremely</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="my-3">
|
||||||
|
<div className="font-semibold ms-1 mb-2">{questions.ease}</div>
|
||||||
|
<Slider
|
||||||
|
field="ease"
|
||||||
|
min={0}
|
||||||
|
max={10}
|
||||||
|
value={form.ease}
|
||||||
|
onChange={(v) => {
|
||||||
|
setForm((prev) => ({ ...prev, ease: v }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="text-sm flex justify-between opacity-80">
|
||||||
|
<div>Not at all</div>
|
||||||
|
<div>Extremely</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="my-3">
|
||||||
|
<div className="font-semibold ms-1 mb-2">
|
||||||
|
{questions.wouldRecommend}
|
||||||
|
</div>
|
||||||
|
<Slider
|
||||||
|
field="ease"
|
||||||
|
min={0}
|
||||||
|
max={10}
|
||||||
|
value={form.wouldRecommend}
|
||||||
|
onChange={(v) => {
|
||||||
|
setForm((prev) => ({ ...prev, wouldRecommend: v }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="text-sm flex justify-between opacity-80">
|
||||||
|
<div>Not at all</div>
|
||||||
|
<div>Extremely</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="my-3">
|
||||||
|
<div className="font-semibold ms-1 mb-3">{questions.hadDifficulty}</div>
|
||||||
|
<RadioGroup
|
||||||
|
direction="vertical"
|
||||||
|
onChange={(e) =>
|
||||||
|
setForm((prev) => ({ ...prev, hadDifficulty: e.target.value }))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Radio value={"yes"}>Yes</Radio>
|
||||||
|
<Radio value={"no"}>No</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
{form.hadDifficulty === "yes" && (
|
||||||
|
<div className="my-3">
|
||||||
|
<div className="font-semibold ms-1 mb-3">{questions.difficulty}</div>
|
||||||
|
<TextArea
|
||||||
|
rows={2}
|
||||||
|
onChange={(v) => setForm((prev) => ({ ...prev, difficulty: v }))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="my-3">
|
||||||
|
<div className="font-semibold ms-1 mb-3">
|
||||||
|
{questions.triedOtherApps}
|
||||||
|
</div>
|
||||||
|
<RadioGroup
|
||||||
|
direction="vertical"
|
||||||
|
onChange={(e) =>
|
||||||
|
setForm((prev) => ({ ...prev, triedOtherApps: e.target.value }))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Radio value={"yes"}>Yes</Radio>
|
||||||
|
<Radio value={"no"}>No</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
{form.triedOtherApps === "yes" && (
|
||||||
|
<div className="my-3">
|
||||||
|
<div className="font-semibold ms-1 mb-3">{questions.comparison}</div>
|
||||||
|
<TextArea
|
||||||
|
rows={2}
|
||||||
|
onChange={(v) => setForm((prev) => ({ ...prev, comparison: v }))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="my-3">
|
||||||
|
<div className="font-semibold ms-1 mb-3">{questions.occupation}</div>
|
||||||
|
<Select
|
||||||
|
optionList={[
|
||||||
|
{ value: "Student", label: "Student" },
|
||||||
|
{ value: "Teacher", label: "Teacher" },
|
||||||
|
{ value: "Developer", label: "Developer" },
|
||||||
|
]}
|
||||||
|
className="w-full"
|
||||||
|
placeholder="Occupation"
|
||||||
|
onSelect={(v) => setForm((prev) => ({ ...prev, occupation: v }))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="ms-1 font-semibold">
|
||||||
|
How can we make drawDB a better experience for you?
|
||||||
|
</div>
|
||||||
|
<RichEditor theme={theme} />
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div className="text-sm opacity-80">
|
||||||
|
<i className="fa-brands fa-markdown me-1"></i>Styling with markdown is
|
||||||
|
supported
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Button
|
||||||
|
onClick={onSubmit}
|
||||||
|
style={{ padding: "16px 32px" }}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
<div className={loading ? "ms-2" : "hidden"}>
|
||||||
|
<Spin />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function Survey() {
|
export default function Survey() {
|
||||||
return <div>Survey</div>;
|
const [theme, setTheme] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const t = localStorage.getItem("theme");
|
||||||
|
setTheme(t);
|
||||||
|
if (t === "dark") {
|
||||||
|
const body = document.body;
|
||||||
|
if (body.hasAttribute("theme-mode")) {
|
||||||
|
body.setAttribute("theme-mode", "dark");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const body = document.body;
|
||||||
|
if (body.hasAttribute("theme-mode")) {
|
||||||
|
body.setAttribute("theme-mode", "light");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.title = "Share you feedback | drawDB";
|
||||||
|
document.body.setAttribute("class", "theme");
|
||||||
|
}, [setTheme]);
|
||||||
|
|
||||||
|
const changeTheme = () => {
|
||||||
|
const body = document.body;
|
||||||
|
const t = body.getAttribute("theme-mode");
|
||||||
|
if (t === "dark") {
|
||||||
|
if (body.hasAttribute("theme-mode")) {
|
||||||
|
body.setAttribute("theme-mode", "light");
|
||||||
|
setTheme("light");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (body.hasAttribute("theme-mode")) {
|
||||||
|
body.setAttribute("theme-mode", "dark");
|
||||||
|
setTheme("dark");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="sm:py-3 py-5 md:px-8 px-8 flex justify-between items-center">
|
||||||
|
<div className="flex items-center justify-start">
|
||||||
|
<img
|
||||||
|
src={theme === "dark" ? logo_dark : logo_light}
|
||||||
|
alt="logo"
|
||||||
|
className="me-2 sm:h-[28px] md:h-[46px]"
|
||||||
|
/>
|
||||||
|
<div className="ms-4 sm:text-sm xl:text-lg font-semibold">
|
||||||
|
Share your feedback
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Button
|
||||||
|
icon={
|
||||||
|
theme === "dark" ? (
|
||||||
|
<IconSun size="extra-large" />
|
||||||
|
) : (
|
||||||
|
<IconMoon size="extra-large" />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
theme="borderless"
|
||||||
|
onClick={changeTheme}
|
||||||
|
></Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr
|
||||||
|
className={`${
|
||||||
|
theme === "dark" ? "border-zinc-700" : "border-zinc-300"
|
||||||
|
} my-1`}
|
||||||
|
/>
|
||||||
|
<div className="md:w-[90%] w-[74%] mx-auto my-8">
|
||||||
|
<Banner
|
||||||
|
fullMode={false}
|
||||||
|
type="info"
|
||||||
|
icon={null}
|
||||||
|
closeIcon={null}
|
||||||
|
description={
|
||||||
|
<div>
|
||||||
|
Thanks for taking this survey! We highly value your feedback and
|
||||||
|
strive to make drawDB fit your needs better.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<LexicalComposer initialConfig={editorConfig}>
|
||||||
|
<SurveyForm theme={theme} />
|
||||||
|
</LexicalComposer>
|
||||||
|
</div>
|
||||||
|
<hr
|
||||||
|
className={`${
|
||||||
|
theme === "dark" ? "border-zinc-700" : "border-zinc-300"
|
||||||
|
} my-1`}
|
||||||
|
/>
|
||||||
|
<div className="text-center text-sm py-3">
|
||||||
|
© 2024 <strong>drawDB</strong> - All right reserved.
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user