// components/CopyButton.tsx // Tiny client component that copies a string to the clipboard with a // transient "Copied" confirmation. Used on read-only detail pages // (e.g. SARS eFiling credentials) where selecting individual // characters by hand is fiddly. Falls back to the legacy textarea // trick when navigator.clipboard is unavailable (e.g. insecure // context) so it still works on http://localhost. "use client"; import { useState } from "react"; interface Props { /** The string to copy when the user clicks. */ value: string; /** Accessible label for the button (also shown as the tooltip * in the idle state). Defaults to "Copy". */ label?: string; /** Optional extra classes appended to the button. */ className?: string; } type State = "idle" | "copied" | "error"; export default function CopyButton({ value, label = "Copy", className = "", }: Props) { const [state, setState] = useState("idle"); async function handleClick() { try { if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(value); } else { // Legacy fallback for browsers that block the async Clipboard // API (e.g. served over plain http://). Mirrors the pattern // used in ShareLinkButton so behaviour is consistent. const ta = document.createElement("textarea"); ta.value = value; ta.setAttribute("readonly", ""); ta.style.position = "fixed"; ta.style.opacity = "0"; document.body.appendChild(ta); ta.select(); document.execCommand("copy"); document.body.removeChild(ta); } setState("copied"); } catch { setState("error"); } // Revert after a short delay so the icon change is noticeable // without sticking around and confusing later clicks. window.setTimeout(() => setState("idle"), 1800); } const title = state === "copied" ? "Copied!" : state === "error" ? "Copy failed — try selecting manually" : label; return ( ); } function IconCopy({ className }: { className?: string }) { return ( ); } function IconCheck({ className }: { className?: string }) { return ( ); } function IconAlert({ className }: { className?: string }) { return ( ); }