Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions integrations/emails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,38 @@ staple.helpdesk@gmail.com
}
}

export function createFeedbackMsg({
fromUsername,
fromEmail,
subject,
message,
}: {
fromUsername: string
fromEmail: string
subject: string
message: string
}) {
return {
from: "STAPLE <app@staplescience.com>",
to: "staple.helpdesk@gmail.com",
replyTo: fromEmail ? `${fromUsername} <${fromEmail}>` : "STAPLE App",
subject: `[Feedback] ${subject}`,
html: `
<html>
<body>
<center><img src="https://raw.githubusercontent.com/STAPLE-verse/STAPLE-verse.github.io/main/pics/staple_email.jpg"
alt="STAPLE Logo" height="200"></center>
<h3>Feedback from ${fromUsername}</h3>
<p><strong>From:</strong> ${fromUsername} &lt;${fromEmail}&gt;</p>
<p><strong>Subject:</strong> ${subject}</p>
<p><strong>Message:</strong></p>
<p>${message.replace(/\n/g, "<br>")}</p>
</body>
</html>
`,
}
}

export function createEditProfileMsg(user) {
return {
from: "STAPLE <app@staplescience.com>",
Expand Down
78 changes: 78 additions & 0 deletions src/core/components/FeedbackButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useRef } from "react"
import { useMutation } from "@blitzjs/rpc"
import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline"
import { z } from "zod"
import sendFeedback from "src/core/mutations/sendFeedback"
import toast from "react-hot-toast"
import { Form } from "src/core/components/fields/Form"
import { LabeledTextField } from "src/core/components/fields/LabeledTextField"
import LabeledTextAreaField from "src/core/components/fields/LabeledTextAreaField"

const FeedbackSchema = z.object({
subject: z.string().min(1, "Subject is required").max(200),
message: z.string().min(1, "Message is required").max(5000),
})

export const FeedbackButton = () => {
const [sendFeedbackMutation] = useMutation(sendFeedback)
const dialogRef = useRef<HTMLDialogElement>(null)

const openModal = () => dialogRef.current?.showModal()
const closeModal = () => dialogRef.current?.close()

const handleSubmit = async (values: { subject: string; message: string }) => {
try {
await sendFeedbackMutation(values)
toast.success("Feedback sent! We'll be in touch.")
closeModal()
} catch {
toast.error("Failed to send feedback. Please try again.")
}
}

return (
<>
<button
onClick={openModal}
className="fixed bottom-6 right-6 z-50 btn btn-circle btn-primary shadow-lg"
title="Send feedback"
>
<QuestionMarkCircleIcon className="w-6 h-6" />
</button>

<dialog
ref={dialogRef}
className="modal"
onClick={(e) => {
if (e.target === dialogRef.current) closeModal()
}}
>
<div className="modal-box" onClick={(e) => e.stopPropagation()}>
<h3 className="font-bold text-lg mb-4">Send Feedback</h3>
<Form
schema={FeedbackSchema}
onSubmit={handleSubmit}
submitText="Send"
cancelText="Cancel"
onCancel={closeModal}
summitOnRight
>
<LabeledTextField
name="subject"
label="Subject"
placeholder="Brief description of your feedback"
className="input mb-4 w-full text-primary input-primary input-bordered border-2 bg-base-300"
/>
<LabeledTextAreaField
name="message"
label="Message"
placeholder="Describe your issue or suggestion..."
className="textarea textarea-primary textarea-bordered border-2 bg-base-300 text-primary mb-4 w-full"
rows={6}
/>
</Form>
</div>
</dialog>
</>
)
}
4 changes: 3 additions & 1 deletion src/core/layouts/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Sidebar from "../components/sidebar/Sidebar"
import { Toaster } from "react-hot-toast"
import useSidebar from "src/core/hooks/useSidebar"
import useExpanded from "src/core/hooks/useExpanded"
import { FeedbackButton } from "../components/FeedbackButton"

const Layout: BlitzLayout<{
title?: string
Expand All @@ -17,7 +18,7 @@ const Layout: BlitzLayout<{

return (
<>
<Toaster position="bottom-center" reverseOrder={false} />
<Toaster position="bottom-left" reverseOrder={false} />
{/* @ts-expect-error false positive: JSX children are valid here */}
<Head>
<title>{title || "STAPLE"}</title>
Expand All @@ -33,6 +34,7 @@ const Layout: BlitzLayout<{
<div className="flex-1 overflow-auto p-4">{children}</div>
</div>
</div>
<FeedbackButton />
</>
)
}
Expand Down
32 changes: 32 additions & 0 deletions src/core/mutations/sendFeedback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { resolver } from "@blitzjs/rpc"
import { z } from "zod"
import db from "db"
import { ResendMsg } from "integrations/mailer"
import { createFeedbackMsg } from "integrations/emails"

const SendFeedbackInput = z.object({
subject: z.string().min(1).max(200),
message: z.string().min(1).max(5000),
})

export default resolver.pipe(
resolver.zod(SendFeedbackInput),
resolver.authorize(),
async ({ subject, message }, ctx) => {
const user = await db.user.findFirst({
where: { id: ctx.session.userId! },
select: { username: true, email: true },
})

await ResendMsg(
createFeedbackMsg({
fromUsername: user?.username ?? "Unknown",
fromEmail: user?.email ?? "Unknown",
subject,
message,
})
)

return { success: true }
}
)
Loading