From 3724fe3be0b377e488ec2431e31c5675d7cd5c2d Mon Sep 17 00:00:00 2001 From: isshaddad Date: Fri, 31 Jul 2026 20:15:39 -0400 Subject: [PATCH 1/4] fix(webapp): stop the sidebar feedback popover from canceling the submit --- .server-changes/fix-sidebar-feedback.md | 6 +++++ apps/webapp/app/components/Feedback.tsx | 21 ++++++++++++++---- .../navigation/HelpAndFeedbackPopover.tsx | 22 ++++++++++++------- 3 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 .server-changes/fix-sidebar-feedback.md 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..09e9ad0a621 100644 --- a/apps/webapp/app/components/Feedback.tsx +++ b/apps/webapp/app/components/Feedback.tsx @@ -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 — pass both to host the dialog outside a popover so the popover closing can't + // unmount the form mid-submit (that teardown was intermittently canceling the feedback POST). + 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(); @@ -79,7 +92,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. */} + ); } From a2aaa36ff557c15087de877d3785e4e57e998fa7 Mon Sep 17 00:00:00 2001 From: isshaddad Date: Fri, 31 Jul 2026 20:33:43 -0400 Subject: [PATCH 2/4] refactor(webapp): type Feedback open/setOpen as an all-or-none pair --- apps/webapp/app/components/Feedback.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/webapp/app/components/Feedback.tsx b/apps/webapp/app/components/Feedback.tsx index 09e9ad0a621..d72542ca4f7 100644 --- a/apps/webapp/app/components/Feedback.tsx +++ b/apps/webapp/app/components/Feedback.tsx @@ -30,11 +30,13 @@ type FeedbackProps = { button?: ReactNode; defaultValue?: FeedbackType; onOpenChange?: (open: boolean) => void; - // Controlled mode — pass both to host the dialog outside a popover so the popover closing can't - // unmount the form mid-submit (that teardown was intermittently canceling the feedback POST). - open?: boolean; - setOpen?: (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, From 7f4a6198248e2cabba4f918479dff1c6a3370265 Mon Sep 17 00:00:00 2001 From: isshaddad Date: Fri, 31 Jul 2026 20:47:06 -0400 Subject: [PATCH 3/4] style(webapp): format Feedback with oxfmt --- apps/webapp/app/components/Feedback.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/webapp/app/components/Feedback.tsx b/apps/webapp/app/components/Feedback.tsx index d72542ca4f7..c4c5f63b30e 100644 --- a/apps/webapp/app/components/Feedback.tsx +++ b/apps/webapp/app/components/Feedback.tsx @@ -30,13 +30,11 @@ type FeedbackProps = { 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 } -); + ({ open?: never; setOpen?: never } | { open: boolean; setOpen: (open: boolean) => void }); export function Feedback({ button, From 0fb709a458b75978dbcd6713dc3a453e9215ef07 Mon Sep 17 00:00:00 2001 From: isshaddad Date: Sun, 2 Aug 2026 09:27:10 -0400 Subject: [PATCH 4/4] fix(webapp): reset feedback topic to default when the dialog closes --- apps/webapp/app/components/Feedback.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/webapp/app/components/Feedback.tsx b/apps/webapp/app/components/Feedback.tsx index c4c5f63b30e..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"; @@ -85,6 +85,18 @@ export function Feedback({ } }, [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);