fix(clipboard): show toast feedback on copy failures - #1385
Open
Rafiat30 wants to merge 1 commit into
Open
Conversation
Copy-address buttons called navigator.clipboard.writeText directly with no .then/.catch, so a denied clipboard permission (or an unavailable Clipboard API) failed silently with no feedback to the user. Add a shared copyToClipboard(text, options?) helper in lib/clipboard.ts that awaits the write and shows a toast.success or toast.error accordingly. It never throws, so callers don't need their own try/catch. Use it at every copy-address call site: - settings page: wallet address copy and contract address copy - WalletButton dropdown: copy wallet address - StreamDetailsModal: copy recipient address (also adds a brief copied-state icon swap, matching the existing wallet address pattern) Add unit tests for the shared util and render tests for each call site that simulate a rejected clipboard write and assert an error toast is shown instead of failing silently.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1225
Summary
Two "copy address" buttons called
navigator.clipboard.writeText(...)directly with no.then/.catchand no toast feedback (a third copy button — the settings page "Contract Address" copier — had the same problem: it firedtoast.successunconditionally and never handled a rejected write). If the browser denied clipboard permission, or the Clipboard API was unavailable, the failure was swallowed silently and the user had no idea the copy didn't happen.This PR adds a shared
copyToClipboard(text, options?)utility infrontend/src/lib/clipboard.tsand uses it at every copy-address call site in the app, so a failed write always surfaces an error toast and a successful write always surfaces a success toast.New files
frontend/src/lib/clipboard.ts— the sharedcopyToClipboardutility.frontend/src/lib/clipboard.test.ts— unit tests for the utility.frontend/src/app/settings/settings-content.test.tsx— render tests covering both copy buttons on the settings page.frontend/src/components/wallet/WalletButton.test.tsx— render tests for the wallet dropdown's copy button.frontend/src/components/dashboard/StreamDetailsModal.test.tsx— render tests for the recipient-address copy button.Modified files
frontend/src/app/settings/settings-content.tsx—copyAddress(wallet address) and the "Contract Address" copy button now go throughcopyToClipboard.frontend/src/components/wallet/WalletButton.tsx—handleCopynow usescopyToClipboardinstead of a bare try/catch that silently swallowed errors.frontend/src/components/dashboard/StreamDetailsModal.tsx— the recipient-address copy button now usescopyToClipboardand gets the same copied-state icon swap (checkmark, 1.5s) used elsewhere in the app.Test files
frontend/src/lib/clipboard.test.tsfrontend/src/app/settings/settings-content.test.tsxfrontend/src/components/wallet/WalletButton.test.tsxfrontend/src/components/dashboard/StreamDetailsModal.test.tsx(The first is new coverage for the utility itself; the other three are new render-level coverage for each call site — none of these components had a test file before.)
Implementation details
copyToClipboard(text, options?)infrontend/src/lib/clipboard.ts:navigator.clipboardmissing, insecure context, unsupported browser) by treating that the same as a rejected write.await navigator.clipboard.writeText(text); on success callstoast.success(options?.successMessage ?? "Copied to clipboard")and resolvestrue.toast.error(options?.errorMessage ?? "Failed to copy to clipboard")and resolvesfalse. It never throws, so no caller needs its own try/catch.options.successMessage/options.errorMessagelet each call site keep its existing, more specific copy (e.g. "Address copied to clipboard", "Recipient address copied", "Contract address copied") while sharing the same success/failure plumbing.copyToClipboard(text, { successMessage }), and only flip local "copied" UI state (checkmark icon, "Copied!" label, tooltip) when the returned promise resolvestrue— so the button never shows a false "copied" state after a failed write.Tests added
clipboard.test.ts: writes text and shows a success toast on success; uses a custom success message when provided; shows an error toast (and does not throw) whenwriteTextrejects, e.g. permission denied; uses a custom error message when provided; shows an error toast whennavigator.clipboardis unavailable entirely.settings-content.test.tsx: for both the wallet-address copy button and the contract-address copy button — shows an error toast (not a silent failure) when the write is denied, and shows a success toast (plus the "Address copied" label swap for the wallet address button) when the write succeeds.WalletButton.test.tsx: shows an error toast instead of failing silently when the clipboard write is denied (and confirms the local "Copied!" label never fires on failure); shows a success toast and toggles the "Copied!" label when the write succeeds.StreamDetailsModal.test.tsx: shows an error toast instead of failing silently when the clipboard write is denied; shows a success toast (plus the checkmark icon swap) when the write succeeds.One non-obvious detail these tests had to work around:
@testing-library/user-event'ssetup()installs its own internal clipboard stub viaObject.definePropertyonnavigator.clipboard. If a test defines its ownnavigator.clipboardmock before callinguserEvent.setup(),setup()silently clobbers it, so the click ends up going through user-event's stub instead of the test's mock (the effect is that the "denied" case looks like it "succeeds" because it never rejects, andwriteTextis never actually invoked/observed). All four test files calluserEvent.setup()first and install thenavigator.clipboardmock afterward, which is required for the mocked rejection to actually reach the component under test.Manual test plan
For each of the three call sites, use Chrome/Edge DevTools to simulate a clipboard permission denial:
chrome://settings/content/clipboardand block clipboard access forlocalhost. (Alternatively, in the DevTools Console, runObject.defineProperty(navigator.clipboard, 'writeText', { value: () => Promise.reject(new Error('denied')) })before clicking, which reliably forces the rejection path without touching browser permission state.)/settings, wallet connected):Automated coverage for all of the above (including the permission-denied path) is included in the four test files listed above; run
npm test --workspace=frontendto execute them.