'use client' import { submitSimilarThreadFeedback, updateSimilarThreadFeedback } from '~/app/contribute/actions' import type { SimilarSolvedThread, SimilarThreadFeedbackReaction, ThreadSource, } from '~/types/contribute' import { ChevronDown, ThumbsDown, ThumbsUp } from 'lucide-react' import { useRef, useState } from 'react' import { Badge, Button, Card, CardContent, CardFooter, CardHeader, cn, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogSection, DialogSectionSeparator, DialogTitle, Label, TextArea, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from 'ui' import { ChannelIcon } from './Icons' function getChannelFromUrl(url: string): ThreadSource { const u = url.toLowerCase() if (u.includes('discord')) return 'discord' if (u.includes('reddit')) return 'reddit' if (u.includes('github')) return 'github' // TODO: handle unknown URL patterns more explicitly, don’t just default to GitHub return 'github' } interface SimilarSolvedThreadsProps { threads: SimilarSolvedThread[] parentThreadId: string } const SimilarThreadCard = ({ thread, className, }: { thread: SimilarSolvedThread className?: string }) => { const channel = getChannelFromUrl(thread.external_activity_url || '') const filteredStack = thread.stack?.filter((s) => s !== 'Other') ?? [] const hasStack = filteredStack.length > 0 const url = thread.external_activity_url || null const linkClassName = cn( 'border-b border-border px-6 py-4 flex items-center gap-3 overflow-hidden hover:bg-surface-200 transition-colors', className ) const content = ( <>

{thread.subject}

{thread.problem_description ? (

{thread.problem_description}

) : null} {hasStack ? (
{filteredStack.map((tech) => ( {tech} ))}
) : null}
) if (url) { return ( {content} ) } return
{content}
} export const SimilarSolvedThreads = ({ threads, parentThreadId }: SimilarSolvedThreadsProps) => { const [isExpanded, setIsExpanded] = useState(true) const [submittedReaction, setSubmittedReaction] = useState( null ) const [feedbackId, setFeedbackId] = useState(null) const [dialogOpen, setDialogOpen] = useState(false) const [dialogReaction, setDialogReaction] = useState('positive') const [dialogFeedback, setDialogFeedback] = useState('') const [isSubmitting, setIsSubmitting] = useState(false) const isClosingProgrammatically = useRef(false) const handleThumbClick = async (reaction: SimilarThreadFeedbackReaction) => { if (submittedReaction) return setIsSubmitting(true) const result = await submitSimilarThreadFeedback({ parentThreadId, reaction, similarThreadKey: null, }) setIsSubmitting(false) if (result.success) { setFeedbackId(result.id ?? null) setDialogReaction(reaction) setDialogFeedback('') setDialogOpen(true) } } const persistAndCloseDialog = async () => { if (!feedbackId) return setIsSubmitting(true) const result = await updateSimilarThreadFeedback( feedbackId, dialogReaction, dialogFeedback.trim() || null ) setIsSubmitting(false) if (result.success) { setSubmittedReaction(dialogReaction) isClosingProgrammatically.current = true setDialogOpen(false) queueMicrotask(() => { isClosingProgrammatically.current = false }) } } const handleDialogOpenChange = (open: boolean) => { setDialogOpen(open) if (!open && feedbackId && !isClosingProgrammatically.current) { persistAndCloseDialog() } } return ( {isExpanded && ( <> {threads.map((thread, idx) => ( ))} {submittedReaction ? ( Thanks for helping improve related threads ) : (
Relevant Irrelevant
)}
)} Help improve related threads How relevant were these threads to your issue?
Relevance