fix(affiliates): add Clipboard API fallback for copy buttons (#153) - #174
Conversation
…stack#153) navigator.clipboard.writeText() fails in contexts where the Clipboard API is unavailable or blocked. The UI also flips to 'Copied!' state before the copy actually succeeds. Changes: - New src/lib/copy-to-clipboard.ts: tries Clipboard API first, falls back to hidden textarea + document.execCommand('copy') - DashboardClient CopyButton: now awaits copy result, only shows 'Copied!' state on success Fixes profullstack#153
Greptile SummaryThis PR fixes a silent copy failure in the affiliate dashboard by introducing a
Confidence Score: 4/5Safe to merge after addressing the textarea DOM leak in the fallback path. The DashboardClient.tsx change is clean and correct. The new utility has one real defect: if execCommand or textarea.select() throws inside the fallback block, removeChild is never called and the off-screen textarea remains in the DOM. The fix is a small try/finally restructure. src/lib/copy-to-clipboard.ts — the fallback error path needs attention to ensure the textarea is always removed from the DOM. Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant CopyButton
participant copyToClipboard
participant ClipboardAPI as navigator.clipboard
participant Fallback as execCommand fallback
User->>CopyButton: click
CopyButton->>copyToClipboard: copyToClipboard(text)
alt Clipboard API available
copyToClipboard->>ClipboardAPI: writeText(text)
ClipboardAPI-->>copyToClipboard: success / throws
copyToClipboard-->>CopyButton: true / fall through
end
alt Clipboard API unavailable or threw
copyToClipboard->>Fallback: create textarea, append, select
Fallback->>Fallback: execCommand("copy")
Fallback-->>copyToClipboard: true / false
copyToClipboard-->>CopyButton: true / false
end
alt "ok === true"
CopyButton->>User: show "Copied!" for 2s
else "ok === false"
CopyButton->>User: no feedback (silent failure)
end
Reviews (1): Last reviewed commit: "fix(affiliates): add Clipboard API fallb..." | Re-trigger Greptile |
| try { | ||
| const textarea = document.createElement("textarea"); | ||
| textarea.value = text; | ||
| // Position off-screen to avoid visual flash | ||
| textarea.style.position = "fixed"; | ||
| textarea.style.left = "-9999px"; | ||
| textarea.style.top = "-9999px"; | ||
| textarea.style.opacity = "0"; | ||
| document.body.appendChild(textarea); | ||
| textarea.focus(); | ||
| textarea.select(); | ||
| const ok = document.execCommand("copy"); | ||
| document.body.removeChild(textarea); | ||
| return ok; | ||
| } catch { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
DOM element leak on fallback error: if
document.execCommand("copy") (or textarea.select()) throws, document.body.removeChild(textarea) is never reached and the off-screen element stays in the DOM indefinitely. Use try/finally to guarantee cleanup.
| try { | |
| const textarea = document.createElement("textarea"); | |
| textarea.value = text; | |
| // Position off-screen to avoid visual flash | |
| textarea.style.position = "fixed"; | |
| textarea.style.left = "-9999px"; | |
| textarea.style.top = "-9999px"; | |
| textarea.style.opacity = "0"; | |
| document.body.appendChild(textarea); | |
| textarea.focus(); | |
| textarea.select(); | |
| const ok = document.execCommand("copy"); | |
| document.body.removeChild(textarea); | |
| return ok; | |
| } catch { | |
| return false; | |
| } | |
| const textarea = document.createElement("textarea"); | |
| textarea.value = text; | |
| // Position off-screen to avoid visual flash | |
| textarea.style.position = "fixed"; | |
| textarea.style.left = "-9999px"; | |
| textarea.style.top = "-9999px"; | |
| textarea.style.opacity = "0"; | |
| document.body.appendChild(textarea); | |
| try { | |
| textarea.focus(); | |
| textarea.select(); | |
| return document.execCommand("copy"); | |
| } catch { | |
| return false; | |
| } finally { | |
| document.body.removeChild(textarea); | |
| } |
| return false; | ||
| } | ||
| } No newline at end of file |
Fix for #153 — Clipboard API fallback for affiliate dashboard copy buttons
Bug
The
CopyButtoncomponent callsnavigator.clipboard.writeText()directly. In browsers or embedded contexts where the Clipboard API is unavailable or blocked by permissions, copying fails silently. The UI also flips to the "Copied!" state immediately without waiting for the copy to succeed.Fix
src/lib/copy-to-clipboard.ts:navigator.clipboard.writeText()first (modern API)document.execCommand("copy")when Clipboard API is unavailable or throwstrue/falseto indicate successawaits the copy resultBounty
💎 uGig Affiliate Testing Bounty
SOL payment address:
0xadf380b5048e9730af0957fd39d5ef1de374475d⭐ Starred profullstack/ugig.net ✅