Skip to content

Commit

Permalink
feat: submit form to help-desk
Browse files Browse the repository at this point in the history
  • Loading branch information
pranavmene2000 committed Dec 18, 2024
1 parent f2d4303 commit 80156cb
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 85 deletions.
43 changes: 22 additions & 21 deletions frontend/src/components/feature/settings/help/SocketIOHealth.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ErrorCallout } from "@/components/common/Callouts/ErrorCallouts"
import SettingsContentContainer from "@/components/layout/Settings/SettingsContentContainer"
import { Badge, Flex, IconButton, Link, Text } from "@radix-ui/themes"
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"
Expand Down Expand Up @@ -40,32 +40,33 @@ const SocketIOHealth = () => {
}, []);

return (
<SettingsContentContainer>
<Stack>
<Heading as='h3' size='3' className="not-cal font-semibold">Realtime Connection Test</Heading>
<Text size='2' color='gray'>If messages on Raven do not appear in realtime, you can inspect your network connection here.</Text>
{!loading && socketPingTest === 'Fail' && <ErrorCallout
message="Realtime connections are not working on your site. Messages won't be refreshed in real-time."
/>}
<Flex gap="9" align="center">
<Flex gap="2" align="center">
<Text size="2" color="gray" as='span' className='font-medium'>Real-time Ping Check:</Text>
<Flex align="center" gap="2">
<Badge color={loading ? 'gray' : socketPingTest === "Pass" ? 'green' : 'red'}>{loading ? 'Loading...' : socketPingTest}</Badge>
{!loading && <IconButton title="Send a ping" aria-label="send a ping" color="gray" size="1" variant="ghost" onClick={onPingCheck}>
<LuRefreshCcw className={clsx(loading ? "animate-spin" : null)} size={12} />
</IconButton>}
</Flex>
<Flex gap="3" align="center" pt='2'>
<Text size="2" color="gray" as='span'>Real-time Ping Check:</Text>
<Flex align="center" gap="2">
<Badge color={loading ? 'gray' : socketPingTest === "Pass" ? 'green' : 'red'}>{loading ? 'Loading...' : socketPingTest}</Badge>
{!loading && <IconButton title="Send a ping" aria-label="send a ping" color="gray" size="1" variant="ghost" onClick={onPingCheck}>
<LuRefreshCcw className={clsx(loading ? "animate-spin" : null)} size={12} />
</IconButton>}
</Flex>
</Flex>

{socketTransportMode && <Flex gap="2" align="center">
<Text size="2" color="gray" as='span' className='font-medium'>SocketIO Transport Mode:</Text>
<Badge color="orange">{socketTransportMode}</Badge>
</Flex>}

<Link ml={"auto"} underline="always" size='2' target="_blank"
{socketTransportMode && <Flex gap="2" align="center">
<Text size="2" color="gray" as='span'>SocketIO Transport Mode:</Text>
<Badge color="orange">{socketTransportMode}</Badge>
</Flex>}
<div className="pt-2">
<Link underline="always" size='2' target="_blank"
title="/app/system-health-report"
href="/app/system-health-report">System Health Report <TbReportAnalytics size='15' />
href="/app/system-health-report"><TbReportAnalytics size='16' className="-mb-0.5 pr-1" />View Full System Health Report
</Link>
</Flex>
</SettingsContentContainer>
</div>
</Stack>
)
}

Expand Down
137 changes: 89 additions & 48 deletions frontend/src/components/feature/settings/help/SupportRequest.tsx
Original file line number Diff line number Diff line change
@@ -1,108 +1,146 @@
import { Box, Button, Dialog, Flex, RadioCards, Text, TextArea, TextField } from "@radix-ui/themes"
import { useMemo, useState } from "react"
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 { MdOutlineFeedback } from "react-icons/md";
import { LuBug } from "react-icons/lu"
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 useCurrentRavenUser from "@/hooks/useCurrentRavenUser"
import { useUserData } from "@/hooks/useUserData";

type Props = {
type TicketType = "Feedback" | "Question" | "Bug"

const subTitles: Record<TicketType, { heading: string, subHeading: string, defaultTextAreaValue: string, footerHeading: ReactNode }> = {
"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: <span>You can also email us at <Link href="mailto:support@thecommit.company" underline="none" size='1' target="_blank">support@thecommit.company</Link> We can't respond to every request but we read all of them.</span>
},
"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: <span>You can also email us at <Link href="mailto:support@thecommit.company" underline="none" size='1' target="_blank">support@thecommit.company</Link></span>
},
"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: <span>You can also email us at <Link href="mailto:support@thecommit.company" underline="none" size='1' target="_blank">support@thecommit.company</Link></span>
},
};

interface CreateSupportTicketDialogProps {
open: boolean
onClose: VoidFunction
}
const CreateSupportTicketDialog = ({ open, onClose }: Props) => {

const CreateSupportTicketDialog = ({ open, onClose }: CreateSupportTicketDialogProps) => {

return (
<Dialog.Root open={open} onOpenChange={onClose}>
<Dialog.Content maxWidth={'580px'} className={DIALOG_CONTENT_CLASS}>
<Dialog.Title mb={'1'}>Help and Support</Dialog.Title>
<Dialog.Description size={'2'}>Create a new support request</Dialog.Description>
<Dialog.Content maxWidth={'700px'} className={clsx(DIALOG_CONTENT_CLASS)}>
<SupportRequestForm onClose={onClose} />
</Dialog.Content>
</Dialog.Root>
)
}

interface SupportRequestFormFields {
requestType: "feedback" | "bug"
ticket_type: TicketType
email: string
description: string
}
const SupportRequestForm = ({ onClose }: { onClose: () => void }) => {
interface SupportRequestFormProps {
onClose: () => void
}

const { myProfile } = useCurrentRavenUser()
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const SupportRequestForm = ({ onClose }: SupportRequestFormProps) => {

const currentUserEmail = useMemo(() => {
const email = myProfile?.name;
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return email && emailRegex.test(email) ? email : "";
}, [myProfile]);
const { name } = useUserData()

const {
control,
register,
formState: { errors },
handleSubmit
handleSubmit,
watch
} = useForm<SupportRequestFormFields>({
defaultValues: {
requestType: "feedback",
email: currentUserEmail
ticket_type: "Feedback",
email: emailRegex.test(name ?? '') ? name : ""
}
})

const [loading, setLoading] = useState(false)
const requestType = watch("ticket_type")

const onSubmit = (data: SupportRequestFormFields) => {
setLoading(true)

// Will send payload to API endpoint later on
const { call, error, loading } = useFrappePostCall('raven.api.support_request.submit_support_request')

setTimeout(() => {
setLoading(false)
onClose()
}, 1500)
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 (
<form onSubmit={handleSubmit(onSubmit)}>
<Dialog.Title>{subTitles[requestType].heading}</Dialog.Title>
<Dialog.Description size={'2'} className="min-h-10 select-none">{subTitles[requestType].subHeading}</Dialog.Description>
<Stack gap="2" pt="3">
<Controller
name="requestType"
name="ticket_type"
control={control}
rules={{
required: 'Request type is required',
validate: (value) => ['feedback', 'bug'].includes(value) || 'Invalid request type'
validate: (value) => Object.keys(subTitles).includes(value) || 'Invalid request type'
}}
render={({ field: { onChange, value } }) => (
<RadioCards.Root
value={value}
onValueChange={onChange}
>
<RadioCards.Item value="feedback">
<RadioCards.Item value="Feedback">
<Flex direction="column" width="100%" gap="1">
<Flex align="center" gap="3">
<MdOutlineFeedback size="16" />
<MdOutlineMessage size="16" />
<Text weight="bold">Feedback</Text>
</Flex>
<Text>We'd love to hear your feedback!</Text>
</Flex>
</RadioCards.Item>
<RadioCards.Item value="bug">
<RadioCards.Item value="Question">
<Flex direction="column" width="100%" gap="1">
<Flex align="center" gap="3">
<LuBug size="16" />
<MdOutlineQuestionMark size="16" />
<Text weight="bold">Question</Text>
</Flex>
</Flex>
</RadioCards.Item>
<RadioCards.Item value="Bug">
<Flex direction="column" width="100%" gap="1">
<Flex align="center" gap="3">
<BiBug size="16" />
<Text weight="bold">Bug</Text>
</Flex>
<Text>Encountered an issue?</Text>
</Flex>
</RadioCards.Item>
</RadioCards.Root>
)}
/>
{errors.requestType && <ErrorText>{errors.requestType.message}</ErrorText>}

{errors.ticket_type && <ErrorText>{errors.ticket_type.message}</ErrorText>}

<Stack>
<Box>
Expand Down Expand Up @@ -137,21 +175,24 @@ const SupportRequestForm = ({ onClose }: { onClose: () => void }) => {
})}
rows={5}
resize='vertical'
placeholder="Please provide detailed information"
placeholder={subTitles[requestType].defaultTextAreaValue}
aria-invalid={errors.description ? 'true' : 'false'}
/>
</Box>
{errors.description && <ErrorText>{errors.description?.message}</ErrorText>}
</Stack>

<HStack justify={'end'} pt='4'>
<Dialog.Close>
<Button color='gray' variant={'soft'} disabled={loading}>Cancel</Button>
</Dialog.Close>
<Button type="submit" disabled={loading} >
{loading ? <Loader className="text-white" /> : null}
Submit
</Button>
<HStack justify="between" pt='4' gap="9">
<Text color="gray" size="1" className="select-none">{subTitles[requestType].footerHeading}</Text>
<Flex gap="2">
<Dialog.Close>
<Button color='gray' variant={'soft'} disabled={loading && !error}>Cancel</Button>
</Dialog.Close>
<Button type="submit" disabled={loading} >
{loading && !error ? <Loader className="text-white" /> : null}
Submit
</Button>
</Flex>
</HStack>
</Stack>
</form>
Expand Down
43 changes: 27 additions & 16 deletions frontend/src/pages/settings/HelpAndSupport.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button, Flex, Link, Text } from "@radix-ui/themes";
import { Button, Code, Flex, Link, Separator, Text } from "@radix-ui/themes";
import { FiExternalLink, FiMail } from "react-icons/fi"
import { useBoolean } from "@/hooks/useBoolean"
import PageContainer from "@/components/layout/Settings/PageContainer"
Expand All @@ -7,6 +7,7 @@ import SettingsPageHeader from "@/components/layout/Settings/SettingsPageHeader"
import { Stack } from "@/components/layout/Stack"
import CreateSupportTicketDialog from "../../components/feature/settings/help/SupportRequest"
import SocketIOHealth from "@/components/feature/settings/help/SocketIOHealth"
import { LuMessageSquareWarning } from "react-icons/lu";

const HelpAndSupport = () => {
const [open, { on, off }] = useBoolean()
Expand All @@ -16,18 +17,25 @@ const HelpAndSupport = () => {
<SettingsContentContainer>
<SettingsPageHeader
title='Help and Support'
description='Your gateway to seamless support, system insights, and community resources.'
/>

<Stack gap="5" pt="4">
<Flex align="center" gap="2">
<Text color="gray" size="2" as='span' className='font-medium'>Looking for help? Raise a new support request, and our team will assist you promptly. </Text>
<Button size="1" onClick={on} variant="soft" title="Click here to send us feedback or file an issue" aria-label="click here to send us feedback or file an issue">
Click here
</Button>
</Flex>
<Stack gap="5">
<Stack>
<Text color="gray" size="2" as='span' className='font-medium'>Have ideas or ran into an issue?</Text>
<div>
<Button size="2" onClick={on} variant="outline"
color='gray'
className="not-cal cursor-pointer"
title="Click here to send us feedback or file an issue" aria-label="click here to send us feedback or file an issue">
<LuMessageSquareWarning /> Contact Us
</Button>
</div>

</Stack>
<Separator size='4' />

<SocketIOHealth />
<Separator size='4' />

<ul className="list-none">
<li>
Expand All @@ -48,17 +56,20 @@ const HelpAndSupport = () => {
href="https://ravenchat.ai">Website <FiExternalLink size='12' />
</Link>
</li>
<li>
<Link underline="always" size='2' target="_blank"
color='gray'
title="support@thecommit.company"
href="mailto:support@thecommit.company">Support Email
</Link>
</li>
</ul>

<Link underline="always" size='2' target="_blank"
title="support@thecommit.company"
href="mailto:support@thecommit.company">Need support? Email us <FiMail size='15' />
</Link>

<Stack justify="end">
<Stack gap='0'>
{/* @ts-expect-error */}
<Text size='2' color='gray'><Text size='4' className="cal-sans text-gray-12 dark:text-white">raven</Text> v{frappe?.boot.versions.raven}</Text>
<Text size='1' color='gray'>Crafted by The Commit Company</Text>
<Text size='3' color='gray'><Text size='4' className="cal-sans text-gray-12 dark:text-white">raven</Text> <Code size='2' variant="ghost">v{frappe?.boot.versions.raven}</Code></Text>
<Text size='2' color='gray'>Crafted by The Commit Company</Text>
</Stack>
</Stack>

Expand Down
40 changes: 40 additions & 0 deletions raven/api/support_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import frappe
import requests
import json

@frappe.whitelist(methods=["POST"])
def submit_support_request(
email,
ticket_type,
subject,
description,
status = "Open",
):
"""
Submit a support ticket using Frappe's web form API.
"""

payload = {
"data": json.dumps({
"raised_by": email,
"subject": subject,
"ticket_type": ticket_type,
"description": description,
"status": status,
"doctype": "HD Ticket",
"web_form_name": "support-ticket",
"via_customer_portal" : 1
}),
"web_form": "support-ticket",
}

response = requests.post(
"https://community.ravenapp.cloud/api/method/frappe.website.doctype.web_form.web_form.accept",
data=payload,
headers={'Content-Type': 'application/x-www-form-urlencoded'}
)

if response.status_code == 200:
return "Ticket submitted successfully"
else:
frappe.throw(_("Failed to submit the ticket"))

0 comments on commit 80156cb

Please sign in to comment.