From 116e513852ab0fc42e2342f33d5390217a6b9c73 Mon Sep 17 00:00:00 2001 From: Duncan Crawbuck Date: Wed, 22 Apr 2026 18:37:07 -0700 Subject: [PATCH] Add logged-in email to docs feedback --- src/components/LoginStatusContext.tsx | 26 ++++++++++++++++++----- src/components/feedback/client.tsx | 17 ++++++++++++++- src/routes/$.tsx | 27 +++++++++++++----------- src/routes/api/feedback.ts | 30 ++++++++++++++++++++++++++- 4 files changed, 81 insertions(+), 19 deletions(-) diff --git a/src/components/LoginStatusContext.tsx b/src/components/LoginStatusContext.tsx index f4e77ea7..86ec8529 100644 --- a/src/components/LoginStatusContext.tsx +++ b/src/components/LoginStatusContext.tsx @@ -3,17 +3,29 @@ import React, { createContext, useContext, useEffect, useState } from "react"; import { LoaderCircle } from "lucide-react"; -const LoginStatusContext = createContext<{ +type AuthSession = { isLoggedIn: boolean | null; isLoading: boolean; -}>({ + email?: string; +}; + +type SessionResponse = { + isLoggedIn?: boolean; + userInfo?: { + email?: string; + }; +}; + +const LoginStatusContext = createContext({ isLoggedIn: null, isLoading: true, + email: undefined, }); export function LoginStatusProvider({ children }: { children: React.ReactNode }) { const [isLoggedIn, setIsLoggedIn] = useState(null); const [isLoading, setIsLoading] = useState(true); + const [email, setEmail] = useState(undefined); useEffect(() => { async function checkAuth() { @@ -24,17 +36,21 @@ export function LoginStatusProvider({ children }: { children: React.ReactNode }) if (response && response.ok) { try { - const session = await response.json(); + const session = (await response.json()) as SessionResponse; setIsLoggedIn(!!(session && session.isLoggedIn)); + setEmail(session.userInfo?.email); } catch (_e) { console.error("Failed to parse session data", _e); setIsLoggedIn(false); + setEmail(undefined); } } else { setIsLoggedIn(false); + setEmail(undefined); } } catch (_err) { setIsLoggedIn(false); + setEmail(undefined); } finally { setIsLoading(false); } @@ -44,13 +60,13 @@ export function LoginStatusProvider({ children }: { children: React.ReactNode }) }, []); return ( - + {children} ); } -function useLoginStatus() { +export function useLoginStatus() { return useContext(LoginStatusContext); } diff --git a/src/components/feedback/client.tsx b/src/components/feedback/client.tsx index 1343c160..2e892bcc 100644 --- a/src/components/feedback/client.tsx +++ b/src/components/feedback/client.tsx @@ -23,6 +23,7 @@ import { type BlockFeedback, type PageFeedback, } from "./schema"; +import { useLoginStatus } from "../LoginStatusContext"; import { z } from "zod/mini"; const rateButtonVariants = cva( @@ -39,6 +40,7 @@ const rateButtonVariants = cva( const pageFeedbackResult = z.extend(pageFeedback, { response: actionResponse, + usedAccountEmail: z.optional(z.boolean()), }); const blockFeedbackResult = z.extend(blockFeedback, { @@ -53,6 +55,7 @@ export function Feedback({ }: { onSendAction: (feedback: PageFeedback) => Promise; }) { + const { isLoading, isLoggedIn, email } = useLoginStatus(); const { pathname: url } = useLocation(); const { previous, setPrevious } = useSubmissionStorage(url, (v) => { const result = pageFeedbackResult.safeParse(v); @@ -71,10 +74,12 @@ export function Feedback({ opinion, message, }; + const usedAccountEmail = !isLoading && !!isLoggedIn && !!email; const response = await onSendAction(feedback); setPrevious({ response, + usedAccountEmail, ...feedback, }); setMessage(""); @@ -128,7 +133,12 @@ export function Feedback({ {previous ? (
-

Thank you for your feedback!

+
+

Thank you for your feedback!

+ {previous.usedAccountEmail ? ( +

If we need more detail, we may follow up by email.

+ ) : null} +
{previous.response?.githubUrl ? ( + {!isLoading && isLoggedIn && email ? ( +

+ If we need to follow up, we may reply to your account email. +

+ ) : null}