diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b4d1f3f9..933f3993 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ exclude: "node_modules|.git" -default_stages: [commit] +default_stages: [pre-commit] fail_fast: false repos: diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 43c2410a..a005a080 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -97,6 +97,8 @@ const router = createBrowserRouter( import('./pages/settings/MessageActions/CreateMessageAction')} /> import('./pages/settings/MessageActions/ViewMessageAction')} /> + + import('./pages/settings/HelpAndSupport')} /> }> } /> diff --git a/frontend/src/components/feature/settings/help/SocketIOHealth.tsx b/frontend/src/components/feature/settings/help/SocketIOHealth.tsx new file mode 100644 index 00000000..f3ac78f5 --- /dev/null +++ b/frontend/src/components/feature/settings/help/SocketIOHealth.tsx @@ -0,0 +1,73 @@ +import { ErrorCallout } from "@/components/common/Callouts/ErrorCallouts" +import { Stack } from "@/components/layout/Stack" +import { Badge, Flex, Heading, IconButton, Link, Text } from "@radix-ui/themes" +import clsx from "clsx" +import { FrappeConfig, FrappeContext, useFrappeEventListener } from "frappe-react-sdk" +import { useContext, useEffect, useState } from "react" +import { LuRefreshCcw } from "react-icons/lu" +import { TbReportAnalytics } from "react-icons/tb" + +const SocketIOHealth = () => { + + const { socket } = useContext(FrappeContext) as FrappeConfig + + const [loading, setLoading] = useState(true) + const [socketPingTest, setSocketPingTest] = useState('Fail') + const [socketTransportMode, setSocketTransportMode] = useState('') + + useFrappeEventListener('pong', () => { + setSocketPingTest('Pass') + setLoading(false) + setSocketTransportMode(socket?.io.engine.transport.name) + }) + + const onPingCheck = () => { + setLoading(true) + socket?.emit('ping') + setTimeout(() => { + setLoading(false) + setSocketTransportMode(s => { + if (!s) { + return '' + } + return s + }) + }, 5000) + } + + useEffect(() => { + setTimeout(onPingCheck, 5000); + }, []); + + return ( + + Realtime Connection Test + If messages on Raven do not appear in realtime, you can inspect your network connection here. + {!loading && socketPingTest === 'Fail' && } + + Real-time Ping Check: + + {loading ? 'Loading...' : socketPingTest} + {!loading && + + } + + + + {socketTransportMode && + SocketIO Transport Mode: + {socketTransportMode} + } +
+ View Full System Health Report + +
+
+ ) +} + +export default SocketIOHealth \ No newline at end of file diff --git a/frontend/src/components/feature/settings/help/SupportRequest.tsx b/frontend/src/components/feature/settings/help/SupportRequest.tsx new file mode 100644 index 00000000..f810cb50 --- /dev/null +++ b/frontend/src/components/feature/settings/help/SupportRequest.tsx @@ -0,0 +1,203 @@ +import { ReactNode } from "react"; +import { Box, Button, Dialog, Flex, Link, RadioCards, Text, TextArea, TextField } from "@radix-ui/themes" +import { Controller, useForm } from "react-hook-form" +import { MdOutlineMessage, MdOutlineQuestionMark } from "react-icons/md"; +import clsx from "clsx"; +import { useFrappePostCall } from "frappe-react-sdk"; +import { toast } from "sonner"; +import { BiBug } from "react-icons/bi"; +import { ErrorText, Label } from "@/components/common/Form" +import { Loader } from "@/components/common/Loader" +import { HStack, Stack } from "@/components/layout/Stack" +import { DIALOG_CONTENT_CLASS } from "@/utils/layout/dialog" +import { useUserData } from "@/hooks/useUserData"; + +type TicketType = "Feedback" | "Question" | "Bug" + +const subTitles: Record = { + "Feedback": { + heading: "Send feedback", + subHeading: "How can we improve Linear? If you have a feature request, can you also share how you would use it and why it's important to you?", + defaultTextAreaValue: "What if...", + footerHeading: You can also email us at support@thecommit.company We can't respond to every request but we read all of them. + }, + "Question": { + heading: "Ask a question", + subHeading: "How can we help? Please share any relevant information we may need to answer your question.", + defaultTextAreaValue: "How do I...", + footerHeading: You can also email us at support@thecommit.company + }, + "Bug": { + heading: "Contact us", + subHeading: "What is the issue? If you're reporting a bug, what are the steps you took so we can reproduce the behaviour?", + defaultTextAreaValue: "Something seems wrong...", + footerHeading: You can also email us at support@thecommit.company + }, +}; + +interface CreateSupportTicketDialogProps { + open: boolean + onClose: VoidFunction +} + +const CreateSupportTicketDialog = ({ open, onClose }: CreateSupportTicketDialogProps) => { + + return ( + + + + + + ) +} + +interface SupportRequestFormFields { + ticket_type: TicketType + email: string + description: string +} +interface SupportRequestFormProps { + onClose: () => void +} + +const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; +const SupportRequestForm = ({ onClose }: SupportRequestFormProps) => { + + const { name } = useUserData() + + const { + control, + register, + formState: { errors }, + handleSubmit, + watch + } = useForm({ + defaultValues: { + ticket_type: "Feedback", + email: emailRegex.test(name ?? '') ? name : "" + } + }) + + const requestType = watch("ticket_type") + + const { call, error, loading } = useFrappePostCall('raven.api.support_request.submit_support_request') + + const onSubmit = (data: SupportRequestFormFields) => { + call({ + email: data.email, + ticket_type: data.ticket_type, + subject: data.description.substring(0, 140), + description: data.description + }) + .then(() => { + toast.success("Form submitted successfully!") + onClose() + }) + } + + return ( +
+ {subTitles[requestType].heading} + {subTitles[requestType].subHeading} + + Object.keys(subTitles).includes(value) || 'Invalid request type' + }} + render={({ field: { onChange, value } }) => ( + + + + + + Feedback + + + + + + + + Question + + + + + + + + Bug + + + + + )} + /> + + {errors.ticket_type && {errors.ticket_type.message}} + + + + + + + {errors.email && {errors.email?.message}} + + + + + +