Fallback affiliate tracking link copy#216
Conversation
Greptile SummaryThis PR adds a Clipboard API fallback for the affiliate offer tracking-link copy button, ensuring the copy works in browsers/contexts where
Confidence Score: 5/5The change is isolated to the copy-button handler and its helper functions; the fallback chain is correct, error state is cleared on every new attempt, and no existing behavior is affected. The copyText → copyWithTextarea fallback chain handles all three outcomes correctly. State management is straightforward: copyError is always cleared at the top of handleCopyTrackingUrl before the new attempt, so stale error messages cannot persist across clicks. The two new tests faithfully exercise the fallback and total-failure paths. The bulk of the diff is Prettier reformatting with no logic changes. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A([User clicks Copy]) --> B[handleCopyTrackingUrl]
B --> C[setCopyError '']
C --> D{navigator.clipboard?.writeText exists?}
D -->|Yes| E[clipboard.writeText]
E -->|resolves| F[return true]
E -->|rejects| G[copyWithTextarea fallback]
D -->|No| G
G --> H[append textarea to DOM, focus + select]
H --> I[document.execCommand 'copy']
I -->|true| J[return true]
I -->|false / throws| K[return false]
F --> L{copied?}
J --> L
K --> L
L -->|true| M[setCopiedTrackingUrl true, button shows 'Copied!']
M --> N[setTimeout 2s, reset to 'Copy']
L -->|false| O[setCopyError 'Copy failed...', button stays 'Copy']
Reviews (3): Last reviewed commit: "Fallback affiliate tracking link copy" | Re-trigger Greptile |
| setCopiedTrackingUrl(true); | ||
| setTimeout(() => setCopiedTrackingUrl(false), 2000); |
There was a problem hiding this comment.
The
setTimeout ID is never stored, so a second click within the 2-second window stacks an additional timer. When the first timer fires it resets copiedTrackingUrl to false early — the button snaps back to "Copy" before the expected interval elapses from the most recent click. Storing the ID in a useRef and calling clearTimeout on entry fixes this.
| setCopiedTrackingUrl(true); | |
| setTimeout(() => setCopiedTrackingUrl(false), 2000); | |
| setCopiedTrackingUrl(true); | |
| if (copyTimerRef.current) clearTimeout(copyTimerRef.current); | |
| copyTimerRef.current = window.setTimeout(() => setCopiedTrackingUrl(false), 2000); |
| describe("OfferDetailClient tracking link copy", () => { | ||
| beforeEach(() => { | ||
| vi.clearAllMocks(); | ||
| mockApi(); | ||
| mockClipboard(vi.fn().mockResolvedValue(undefined)); | ||
| mockExecCommand(true); | ||
| }); | ||
|
|
There was a problem hiding this comment.
Missing test for the primary (happy-path) copy flow — where
navigator.clipboard.writeText resolves successfully. The beforeEach wires up a resolving writeText mock but no test exercises that path and asserts that the button transitions from "Copy" → "Copied!" without invoking document.execCommand. Without this, a regression that silently skips the Clipboard API or always falls through to the textarea path would go undetected.
Summary
Fixes #155
Validation
uGig gig: https://ugig.net/gig/cmexbmp260001ju04n3qmkdko