diff --git a/apps/web/src/app/dashboard/insights/page.tsx b/apps/web/src/app/dashboard/insights/page.tsx index b378ecb..75568d9 100644 --- a/apps/web/src/app/dashboard/insights/page.tsx +++ b/apps/web/src/app/dashboard/insights/page.tsx @@ -1,5 +1,6 @@ import { generateAiInsights } from "@/actions/insights"; import ReactMarkdown from "react-markdown"; +import { PreBlock } from "@/components/copy-button"; export const maxDuration = 30; @@ -13,7 +14,15 @@ export default async function InsightsPage() {
- {insights} + ( + {children} + ), + }} + > + {insights} +
); diff --git a/apps/web/src/components/copy-button.tsx b/apps/web/src/components/copy-button.tsx new file mode 100644 index 0000000..aa0ccdf --- /dev/null +++ b/apps/web/src/components/copy-button.tsx @@ -0,0 +1,134 @@ +"use client"; + +import { useState, useCallback, useRef, useEffect, type ReactNode } from "react"; + +interface CopyButtonProps { + text: string; + className?: string; +} + +export function CopyButton({ text, className = "" }: CopyButtonProps) { + const [copied, setCopied] = useState(false); + const timeoutRef = useRef(null); + + useEffect(() => { + return () => { + if (timeoutRef.current) clearTimeout(timeoutRef.current); + }; + }, []); + + const handleCopy = useCallback(async () => { + try { + await navigator.clipboard.writeText(text); + } catch { + // Fallback for older browsers + const textarea = document.createElement("textarea"); + textarea.value = text; + textarea.style.position = "fixed"; + textarea.style.opacity = "0"; + document.body.appendChild(textarea); + textarea.select(); + document.execCommand("copy"); + document.body.removeChild(textarea); + } + setCopied(true); + if (timeoutRef.current) clearTimeout(timeoutRef.current); + timeoutRef.current = setTimeout(() => setCopied(false), 2000); + }, [text]); + + return ( + + ); +} + +interface CodeBlockProps { + children: ReactNode; + className?: string; +} + +export function CodeBlock({ children, className = "" }: CodeBlockProps) { + const codeRef = useRef(null); + const [codeText, setCodeText] = useState(""); + + useEffect(() => { + if (codeRef.current) { + setCodeText(codeRef.current.textContent || ""); + } + }, [children]); + + return ( +
+
+ +
+ + {children} + +
+ ); +} + +interface PreBlockProps { + children: ReactNode; + className?: string; +} + +export function PreBlock({ children, className = "" }: PreBlockProps) { + const preRef = useRef(null); + const [codeText, setCodeText] = useState(""); + + useEffect(() => { + if (preRef.current) { + const codeEl = preRef.current.querySelector("code"); + setCodeText((codeEl || preRef.current).textContent || ""); + } + }, [children]); + + return ( +
+
+ +
+
+        {children}
+      
+
+ ); +}