From 02b0a2db324d21d591d64837d45ebff8a9ad2912 Mon Sep 17 00:00:00 2001 From: Charlie Egan Date: Thu, 5 Jun 2025 10:31:05 +0100 Subject: [PATCH] website: Hide feedback popup when user seen form Signed-off-by: Charlie Egan --- docs/src/components/FeedbackForm/index.js | 346 +++++++++++++--------- 1 file changed, 203 insertions(+), 143 deletions(-) diff --git a/docs/src/components/FeedbackForm/index.js b/docs/src/components/FeedbackForm/index.js index d65098e951..cbde902ebf 100644 --- a/docs/src/components/FeedbackForm/index.js +++ b/docs/src/components/FeedbackForm/index.js @@ -1,35 +1,73 @@ import React, { useEffect, useState, useRef } from "react"; +import { Icon } from "@iconify/react"; import BrowserOnly from "@docusaurus/BrowserOnly"; -import { Icon } from "@iconify/react"; import Admonition from "@theme/Admonition"; import styles from "./styles.module.css"; +/** + * A feedback form component that is automatically added to the end of documentation pages. + * It can also be manually included in other pages by setting enablePopup to true. + * The form allows users to provide positive or negative feedback with optional comments + * and email for follow-up. Feedback is submitted to Netlify Forms via AJAX. + * + * Note: When adding new fields to this form, remember to update the static/netlify-forms.html + * file so that Netlify knows which fields to permit. If this is not updated, new fields + * in the form will be dropped. + */ export default function FeedbackForm({ enablePopup = false }) { + // this is used to ensure the response is categorized in netlify forms and that + // local state is remembered. const formName = "page-feedback"; + const [feedbackType, setFeedbackType] = useState(""); const [comment, setComment] = useState(""); const [url, setUrl] = useState(""); const [path, setPath] = useState(""); const [message, setMessage] = useState(""); + const [showFloatingPopup, setShowFloatingPopup] = useState(false); const [hasScrolled, setHasScrolled] = useState(false); + const [popupEnabled, setPopupEnabled] = useState(enablePopup); const feedbackFormRef = useRef(null); + // The popup will never show before the user has been on the page for 10s + const feedbackPopupTimeoutMs = 10000; + useEffect(() => { if (typeof window !== "undefined") { - if (window.location.hostname === "localhost") { - // setMessage("Feedback form disabled on localhost."); - // return; - } setUrl(window.location); setPath(window.location.pathname); } }, []); + // This effect sets up an intersection observer to detect when the feedback form + // comes into view. When the form is visible, it permanently disables the popup + // to prevent it from showing again. This ensures users who have found the form + // don't get interrupted by the popup. useEffect(() => { - if (enablePopup) { + if (feedbackFormRef.current) { + const observer = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting) { + setShowFloatingPopup(false); + setPopupEnabled(false); + } + }, + { threshold: 0.1 } + ); + + observer.observe(feedbackFormRef.current); + + return () => { + observer.disconnect(); + }; + } + }, [feedbackFormRef.current]); + + useEffect(() => { + if (popupEnabled) { const handleScroll = () => { if (window.scrollY > 100) { setHasScrolled(true); @@ -43,19 +81,21 @@ export default function FeedbackForm({ enablePopup = false }) { if (hasScrolled) { setShowFloatingPopup(true); } - }, 10000); + }, feedbackPopupTimeoutMs); return () => { clearTimeout(timer); window.removeEventListener("scroll", handleScroll); }; } - }, [enablePopup, hasScrolled]); + }, [popupEnabled, hasScrolled]); + // feedback is stored in local storage to prevent the form / popup from showing again useEffect(() => { const feedbackSubmitted = localStorage.getItem(`${formName}-${path}`); if (feedbackSubmitted) { setMessage("Thank you for your feedback!"); + setPopupEnabled(false); } }, [path]); @@ -91,151 +131,171 @@ export default function FeedbackForm({ enablePopup = false }) { } }; - const FloatingPopup = () => ( -
- -
-

How's this page?

-
- - -
-
-
- ); - if (message !== "") { return ( - {() => { - return ( + {() => ( + } + title="Feedback" + > +

{message}

+ +

+ Got more to say? Questions for the OPA experts? Please come and find us on the{" "} + + OPA Slack. + {" "} + The #help channel is a great place to get stated. +

+
+ )} +
+ ); + } + + return ( + + {() => ( + <> + {popupEnabled && showFloatingPopup && ( + setShowFloatingPopup(false)} + onFeedbackSelect={(type) => { + setFeedbackType(type); + scrollToFeedback(); + }} + /> + )} +
} title="Feedback" > -

{message}

- -

- Got more to say? Questions for the OPA experts? Please come and find us on the{" "} - - OPA Slack. - {" "} - The #help channel is a great place to get stated. -

+

We are always trying to make our documentation the best it can be and welcome your comments.

+
- ); - }} - - ); - } - - // NOTE: remember to update the static/netlify-forms.html file so that they - // know which files to permit. If this is not updated, new fields in the - // form will be dropped. - return ( - - {() => { - return ( - <> - {enablePopup && showFloatingPopup && } -
- } - title="Feedback" - > -

We are always trying to make our documentation the best it can be and welcome your comments.

- - - - - -
- - -
- - {feedbackType && ( -
-
-