Add affiliate tracking link copy fallback - #157
Conversation
Greptile SummaryThis PR improves the affiliate tracking-link Copy button by adding a
Confidence Score: 4/5Safe to merge — the change is confined to a single client component and the fallback logic is well-guarded. The core copy-with-fallback logic is correct and the error path works. The uncleared setTimeout on rapid re-clicks is a minor UX rough edge rather than a data or functional break; the deprecated execCommand path is a reasonable stopgap. src/app/affiliates/[slug]/OfferDetailClient.tsx — specifically the timeout management in handleCopyTrackingUrl. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User clicks Copy button] --> B[handleCopyTrackingUrl]
B --> C{navigator.clipboard available?}
C -- Yes --> D[clipboard.writeText]
D -- Success --> E[setTrackingCopied = true]
D -- Error --> F[Fall through to textarea]
C -- No --> F
F --> G[Create hidden textarea, append to body, select text]
G --> H[document.execCommand copy]
H -- true --> E
H -- false --> I[setError: manual copy message]
E --> J[setTimeout 2s]
J --> K[setTrackingCopied = false]
Reviews (1): Last reviewed commit: "Add affiliate tracking link copy fallbac..." | Re-trigger Greptile |
| async function handleCopyTrackingUrl() { | ||
| setError(""); | ||
| setTrackingCopied(false); | ||
|
|
||
| if (await copyText(trackingUrl)) { | ||
| setTrackingCopied(true); | ||
| window.setTimeout(() => setTrackingCopied(false), 2000); | ||
| return; | ||
| } | ||
|
|
||
| setError("Unable to copy tracking link. Select the link and copy it manually."); | ||
| } |
There was a problem hiding this comment.
Stale timeout on rapid re-clicks or unmount
Each call to handleCopyTrackingUrl schedules a new setTimeout without cancelling the previous one. If the user clicks Copy twice within 2 seconds, the first timeout fires and resets the "Copied" state prematurely — before 2 seconds have elapsed since the second click. Additionally, if the component unmounts while the timer is pending, the timeout still fires and calls setTrackingCopied(false) on the stale closure. Storing the timer ID in a useRef and clearing it at the top of the handler (and in a useEffect cleanup) would prevent both issues.
| const textarea = document.createElement("textarea"); | ||
| textarea.value = text; | ||
| textarea.setAttribute("readonly", ""); | ||
| textarea.style.position = "fixed"; | ||
| textarea.style.opacity = "0"; | ||
| document.body.appendChild(textarea); | ||
| textarea.select(); | ||
|
|
||
| try { | ||
| return document.execCommand("copy"); | ||
| } finally { | ||
| document.body.removeChild(textarea); | ||
| } |
There was a problem hiding this comment.
document.execCommand("copy") is deprecated and may silently return false or throw in some browsers even when the selection succeeds. Adding textarea.focus() before .select() improves reliability across browsers.
| const textarea = document.createElement("textarea"); | |
| textarea.value = text; | |
| textarea.setAttribute("readonly", ""); | |
| textarea.style.position = "fixed"; | |
| textarea.style.opacity = "0"; | |
| document.body.appendChild(textarea); | |
| textarea.select(); | |
| try { | |
| return document.execCommand("copy"); | |
| } finally { | |
| document.body.removeChild(textarea); | |
| } | |
| const textarea = document.createElement("textarea"); | |
| textarea.value = text; | |
| textarea.setAttribute("readonly", ""); | |
| textarea.style.position = "fixed"; | |
| textarea.style.top = "0"; | |
| textarea.style.opacity = "0"; | |
| document.body.appendChild(textarea); | |
| textarea.focus(); | |
| textarea.select(); | |
| try { | |
| return document.execCommand("copy"); | |
| } finally { | |
| document.body.removeChild(textarea); | |
| } |
Summary
Closes #155
Validation
pnpm exec eslint 'src/app/affiliates/[slug]/OfferDetailClient.tsx'pnpm type-checkgit diff --check -- 'src/app/affiliates/[slug]/OfferDetailClient.tsx'Paid task
Submitted for uGig paid task
4741218f-a723-46bb-82cb-6516120331ae.Payment address:
27sdMYXofqoM9qR13bZhccRNYeEgYn5EoHXTSJn4QWKPPayment fallback: PayPal cultofrozen@gmail.com