Skip to content

fix(affiliates): add Clipboard API fallback for copy buttons (#153) - #174

Merged
ralyodio merged 1 commit into
profullstack:masterfrom
nguyenlnp:fix/clipboard-api-fallback-153
May 23, 2026
Merged

fix(affiliates): add Clipboard API fallback for copy buttons (#153)#174
ralyodio merged 1 commit into
profullstack:masterfrom
nguyenlnp:fix/clipboard-api-fallback-153

Conversation

@nguyenlnp

Copy link
Copy Markdown
Contributor

Fix for #153 — Clipboard API fallback for affiliate dashboard copy buttons

Bug

The CopyButton component calls navigator.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

  1. New utility src/lib/copy-to-clipboard.ts:
    • Tries navigator.clipboard.writeText() first (modern API)
    • Falls back to hidden textarea + document.execCommand("copy") when Clipboard API is unavailable or throws
    • Returns true/false to indicate success
  2. DashboardClient CopyButton updated:
    • Now awaits the copy result
    • Only shows "Copied!" state on successful copy
    • No more false-positive feedback

Bounty

💎 uGig Affiliate Testing Bounty
SOL payment address: 0xadf380b5048e9730af0957fd39d5ef1de374475d
⭐ Starred profullstack/ugig.net ✅

…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-apps

greptile-apps Bot commented May 23, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a silent copy failure in the affiliate dashboard by introducing a copyToClipboard utility that tries the modern Clipboard API first and falls back to the deprecated-but-widely-supported execCommand("copy") approach. The CopyButton component is updated to await the result and only show "Copied!" on success.

  • src/lib/copy-to-clipboard.ts (new): Two-tier copy utility — Clipboard API with execCommand fallback — returning a boolean success indicator.
  • src/app/dashboard/affiliates/DashboardClient.tsx: CopyButton now uses await copyToClipboard(text) and gates the success state on the returned boolean.

Confidence Score: 4/5

Safe 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

Filename Overview
src/lib/copy-to-clipboard.ts New utility with correct two-tier Clipboard API + execCommand fallback, but the fallback block leaks the textarea element into the DOM if execCommand or select() throws.
src/app/dashboard/affiliates/DashboardClient.tsx CopyButton updated to await the new utility and only set the Copied! state on success — straightforward and correct change.

Sequence Diagram

sequenceDiagram
    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
Loading

Reviews (1): Last reviewed commit: "fix(affiliates): add Clipboard API fallb..." | Re-trigger Greptile

Comment on lines +20 to +36
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;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Suggested change
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);
}

Comment on lines +35 to +37
return false;
}
} No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The file is missing a trailing newline, which can cause noisy diffs and breaks POSIX compliance for text files.

Suggested change
return false;
}
}
return false;
}
}

@ralyodio
ralyodio merged commit 52c9491 into profullstack:master May 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants