diff --git a/.server-changes/fix-sidebar-feedback.md b/.server-changes/fix-sidebar-feedback.md new file mode 100644 index 00000000000..066332c0314 --- /dev/null +++ b/.server-changes/fix-sidebar-feedback.md @@ -0,0 +1,6 @@ +--- +area: webapp +type: fix +--- + +Fix the sidebar "Help & Feedback → Contact us" form sometimes not sending your message. It now sends reliably every time. diff --git a/apps/webapp/app/components/Feedback.tsx b/apps/webapp/app/components/Feedback.tsx index ebd61180de5..b679b042da1 100644 --- a/apps/webapp/app/components/Feedback.tsx +++ b/apps/webapp/app/components/Feedback.tsx @@ -9,7 +9,7 @@ import { parseWithZod } from "@conform-to/zod"; import { InformationCircleIcon, ArrowUpCircleIcon } from "@heroicons/react/20/solid"; import { EnvelopeIcon, ShieldCheckIcon } from "@heroicons/react/24/solid"; import { Form, useActionData, useLocation, useNavigation, useSearchParams } from "@remix-run/react"; -import { type ReactNode, useEffect, useState } from "react"; +import { type ReactNode, useEffect, useRef, useState } from "react"; import { type FeedbackType, feedbackTypes, schema } from "~/routes/resources.feedback"; import { Button } from "./primitives/Buttons"; import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "./primitives/Dialog"; @@ -27,13 +27,26 @@ import { TextLink } from "./primitives/TextLink"; import { DialogClose } from "@radix-ui/react-dialog"; type FeedbackProps = { - button: ReactNode; + button?: ReactNode; defaultValue?: FeedbackType; onOpenChange?: (open: boolean) => void; -}; +} & + // Controlled mode is all-or-none: pass both open + setOpen to host the dialog outside a popover + // (so the popover closing can't unmount the form mid-submit and cancel the feedback POST), or + // neither for the self-managed, button-triggered dialog. Passing only one is a broken half-state. + ({ open?: never; setOpen?: never } | { open: boolean; setOpen: (open: boolean) => void }); -export function Feedback({ button, defaultValue = "bug", onOpenChange }: FeedbackProps) { - const [open, setOpen] = useState(false); +export function Feedback({ + button, + defaultValue = "bug", + onOpenChange, + open: openProp, + setOpen: setOpenProp, +}: FeedbackProps) { + const [openState, setOpenState] = useState(false); + // Controlled when the caller passes open/setOpen (hosted outside a popover); otherwise self-managed. + const open = openProp ?? openState; + const setOpen = setOpenProp ?? setOpenState; const [searchParams, setSearchParams] = useSearchParams(); const location = useLocation(); const lastSubmission = useActionData(); @@ -72,6 +85,18 @@ export function Feedback({ button, defaultValue = "bug", onOpenChange }: Feedbac } }, [searchParams]); + // Reset the topic to the default once the dialog closes, so reopening always starts fresh. The + // dialog is now persistently mounted (hosted outside the popover), so without this it would keep + // the previously chosen topic selected and risk filing feedback under the wrong category. Keyed + // on the close transition (not just `!open`) so the ?feedbackPanel= open path isn't clobbered. + const wasOpen = useRef(open); + useEffect(() => { + if (wasOpen.current && !open) { + setType(defaultValue); + } + wasOpen.current = open; + }, [open, defaultValue]); + const handleOpenChange = (value: boolean) => { setOpen(value); onOpenChange?.(value); @@ -79,7 +104,7 @@ export function Feedback({ button, defaultValue = "bug", onOpenChange }: Feedbac return ( - {button} + {button ? {button} : null} Contact us
diff --git a/apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx b/apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx index 669271fe766..c7276dd5bc6 100644 --- a/apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx +++ b/apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx @@ -35,6 +35,10 @@ export function HelpAndFeedback({ projectId?: string; }) { const [isHelpMenuOpen, setHelpMenuOpen] = useState(false); + // Hosted outside the popover (below) and opened from the menu item, so the popover closing never + // unmounts the feedback form mid-submit — that teardown was intermittently canceling the POST to + // /resources/feedback, so messages sent from the sidebar were silently lost. + const [isFeedbackOpen, setFeedbackOpen] = useState(false); const _currentPlan = useCurrentPlan(); const { changelogs } = useRecentChangelogs(organizationId, projectId); @@ -164,14 +168,14 @@ export function HelpAndFeedback({ target="_blank" /> - - } + { + setHelpMenuOpen(false); + setFeedbackOpen(true); + }} />
@@ -206,6 +210,8 @@ export function HelpAndFeedback({ )} + {/* Hosted outside the popover so closing the menu can't unmount the form mid-submit. */} + ); }