'use client' import { createClient } from '@supabase/supabase-js' import { IS_PLATFORM } from '~/lib/constants' import { useSendTelemetryEvent } from '~/lib/telemetry' import { gotrueClient, useConstant, useIsLoggedIn, type Database } from 'common' import { Check, MessageSquareQuote, X } from 'lucide-react' import { usePathname } from 'next/navigation' import { forwardRef, useReducer, useRef, useState, type CSSProperties, type MouseEventHandler, } from 'react' import { Button, cn } from 'ui' import { getSanitizedTabParams } from './Feedback.utils' import { FeedbackModal, type FeedbackFields } from './FeedbackModal' const FeedbackButton = forwardRef< HTMLButtonElement, { isYes: boolean; onClick: MouseEventHandler; visible: boolean } >(({ isYes, onClick, visible }, ref) => { const isLoggedIn = useIsLoggedIn() if (!isLoggedIn) return null return ( ) }) FeedbackButton.displayName = 'FeedbackButton' type Response = 'yes' | 'no' enum StateType { Unanswered = 'unanswered', Followup = 'followup', } type State = { type: StateType.Unanswered } | { type: StateType.Followup; response: Response } type Action = { event: 'VOTED'; response: Response } const initialState = { type: StateType.Unanswered } satisfies State function reducer(state: State, action: Action) { switch (action.event) { case 'VOTED': if (state.type === StateType.Unanswered) return { type: StateType.Followup, response: action.response } default: return state } } function Feedback({ className }: { className?: string }) { const [state, dispatch] = useReducer(reducer, initialState) const [modalOpen, setModalOpen] = useState(false) const feedbackButtonRef = useRef(null) const pathname = usePathname() ?? '' const sendTelemetryEvent = useSendTelemetryEvent() const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY const supabase = useConstant(() => IS_PLATFORM && supabaseUrl && supabaseAnonKey ? createClient(supabaseUrl, supabaseAnonKey) : undefined ) const unanswered = state.type === 'unanswered' const isYes = 'response' in state && state.response === 'yes' const isNo = 'response' in state && state.response === 'no' const showYes = unanswered || isYes const showNo = unanswered || isNo async function sendFeedbackVote(response: Response) { if (!supabase) return const { error } = await supabase .from('feedback') .insert({ vote: response, page: pathname, metadata: { query: getSanitizedTabParams() } }) if (error) console.error(error) } function handleVote(response: Response) { sendTelemetryEvent({ action: 'docs_feedback_clicked', properties: { response }, }) sendFeedbackVote(response) dispatch({ event: 'VOTED', response }) // Focus so screen reader users are aware of the new element setTimeout(() => { feedbackButtonRef.current?.focus() // Wait for element to show up first }, 700) } function refocusButton() { setTimeout(() => { feedbackButtonRef.current?.focus() // Wait for modal to disappear and button to become focusable again }, 100) } async function handleSubmit({ comment, title }: FeedbackFields) { if (supabase) { const userId = (await gotrueClient.getSession()).data.session?.user?.id ?? null const { error } = await supabase.from('feedback_comments').insert({ page: pathname, // @ts-expect-error -- the comment modal only opens after a vote, so state.response is set vote: state.response, title, comment, user_id: userId, metadata: { query: getSanitizedTabParams() }, }) if (error) console.error(error) } setModalOpen(false) refocusButton() } return (

Is this helpful?

{state.type === StateType.Followup && ( <> Thanks for your feedback! setModalOpen(true)} isYes={isYes} visible={true} /> )}
{ setModalOpen(false) refocusButton() }} onSubmit={handleSubmit} />
) } export { Feedback }