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 (
+
+ );
+}
+
+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 (
+
+ );
+}