Add reusable clipboard utility and copy button#458
Add reusable clipboard utility and copy button#458barry01-hash wants to merge 3 commits intotheblockcade:mainfrom
Conversation
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 9 minutes and 34 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughAdds a new CopyButton component and a clipboard utility with Clipboard API + execCommand fallback, plus tests and index re-exports; CopyButton manages explicit copy states, error propagation, callbacks, and timed UI feedback. Changes
Sequence DiagramsequenceDiagram
actor User
participant Button as CopyButton
participant Clipboard as writeToClipboard
participant API as Navigator.clipboard
participant Fallback as execCommand
participant Store as ErrorStore
participant Callback as onCopy/onCopyError
User->>Button: Click
Button->>Button: status = "copying"
Button->>Clipboard: writeToClipboard(text)
alt Clipboard API available
Clipboard->>API: writeText()
alt success
API-->>Clipboard: resolves
Clipboard-->>Button: {ok:true, method:'clipboard-api'}
Button->>Button: status = "copied"
Button->>Callback: onCopy()
Button->>Button: schedule reset (feedbackDurationMs)
else failure
API-->>Clipboard: rejects
Clipboard->>Fallback: attempt execCommand copy
alt execCommand success
Fallback-->>Clipboard: {ok:true, method:'exec-command'}
Clipboard-->>Button: success
Button->>Callback: onCopy()
else execCommand failure
Fallback-->>Clipboard: {ok:false, reason:'write-failed', error}
Clipboard-->>Button: failure
Button->>Store: setError(error)
Button->>Callback: onCopyError(error)
end
end
else Clipboard API unavailable
Clipboard->>Fallback: attempt execCommand copy
alt execCommand available & success
Fallback-->>Clipboard: {ok:true, method:'exec-command'}
Clipboard-->>Button: success
Button->>Callback: onCopy()
else neither available or failure
Clipboard-->>Button: {ok:false, reason:'unsupported' or 'write-failed', error}
Button->>Store: setError(error)
Button->>Callback: onCopyError(error)
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
frontend/tests/components/v1/CopyButton.test.tsx (1)
41-105: Consider adding component-level regressions for fallback + callback rejection.Add tests for:
- Clipboard API unavailable but
execCommandsucceeds (button still reaches copied state), andonCopyrejecting (button does not remain stuck in copying/disabled state).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/tests/components/v1/CopyButton.test.tsx` around lines 41 - 105, Add two tests to cover fallback and rejection: (1) simulate Clipboard API missing by deleting Navigator.prototype.clipboard and mock Document.prototype.execCommand to return true, then render <CopyButton ... />, click the button and assert it reaches the "copied" UI (idleLabel -> copiedLabel and copy-button-status shows success) and any success callback is invoked; reference the CopyButton component and use Document.prototype.execCommand mock for the fallback path. (2) Simulate onCopy rejecting by passing an onCopy mock that returns a rejected promise, render <CopyButton ... />, click the button and assert the button does not remain disabled/stuck in a copying state (check it returns to idleLabel and is enabled) and any onCopyError is called if provided; reference the onCopy and onCopyError props and ensure timers (vi.useFakeTimers / vi.advanceTimersByTime) are used if feedbackDurationMs is involved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/components/v1/CopyButton.tsx`:
- Around line 95-106: The handler can get stuck in "copying" because await
onCopy?.() runs before setStatus('copied'); wrap the consumer callback so the
component state is updated regardless: either call setStatus('copied') and
scheduleReset() before awaiting onCopy, or (preferable) run await onCopy?.()
inside a try/catch and put setStatus('copied') and scheduleReset() in a finally
block so they always run; if the consumer throws, call onCopyError and
setError/setGlobalError with the error inside the catch to preserve existing
error handling (refer to onCopy, onCopyError, setStatus, scheduleReset,
setError, setGlobalError).
---
Nitpick comments:
In `@frontend/tests/components/v1/CopyButton.test.tsx`:
- Around line 41-105: Add two tests to cover fallback and rejection: (1)
simulate Clipboard API missing by deleting Navigator.prototype.clipboard and
mock Document.prototype.execCommand to return true, then render <CopyButton ...
/>, click the button and assert it reaches the "copied" UI (idleLabel ->
copiedLabel and copy-button-status shows success) and any success callback is
invoked; reference the CopyButton component and use
Document.prototype.execCommand mock for the fallback path. (2) Simulate onCopy
rejecting by passing an onCopy mock that returns a rejected promise, render
<CopyButton ... />, click the button and assert the button does not remain
disabled/stuck in a copying state (check it returns to idleLabel and is enabled)
and any onCopyError is called if provided; reference the onCopy and onCopyError
props and ensure timers (vi.useFakeTimers / vi.advanceTimersByTime) are used if
feedbackDurationMs is involved.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 39cee79e-3168-423a-a2c0-376dbea160a0
📒 Files selected for processing (6)
frontend/src/components/v1/CopyButton.tsxfrontend/src/components/v1/index.tsfrontend/src/utils/v1/clipboard.tsfrontend/src/utils/v1/index.tsfrontend/tests/components/v1/CopyButton.test.tsxfrontend/tests/utils/v1/clipboard.test.ts
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
frontend/src/components/v1/CopyButton.tsx (1)
95-106:⚠️ Potential issue | 🟠 MajorCallback failures can leave button stuck in
copyingstate.If
onCopy?.()throws at line 96, execution stops beforesetStatus('copied')(line 97), leaving the button disabled. Same issue exists foronCopyErrorat line 105, though state is already updated by then.Proposed fix: update state before awaiting callbacks
if (result.ok) { - await onCopy?.(); setStatus('copied'); scheduleReset(); + try { + await onCopy?.(); + } catch { + // Consumer callback failure shouldn't break button UX + } return; } setStatus('idle'); setError(result.error); setGlobalError(result.error); - await onCopyError?.(result.error); + try { + await onCopyError?.(result.error); + } catch { + // Consumer callback failure shouldn't bubble + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/v1/CopyButton.tsx` around lines 95 - 106, The CopyButton handler can get stuck in "copying" if user-supplied callbacks throw; update state before awaiting callbacks and guard callback calls with try/catch to prevent thrown errors from aborting state updates. Specifically, in the copy handler inside the CopyButton component setStatus('copied') and call scheduleReset() before awaiting onCopy, and in the error path setStatus('idle') and setError/setGlobalError before awaiting onCopyError; additionally wrap await onCopy(...) and await onCopyError(...) in try/catch (or call them without awaiting) so exceptions don’t prevent the prior state changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/components/v1/CopyButton.tsx`:
- Around line 1-139: This file defines two conflicting CopyButton
implementations (the top exported function CopyButton and a later const
CopyButton FC with a different API); remove the OLD const FC implementation (the
later CopyButton definition and its related export and props
variant/onCopySuccess) so only the new function CopyButton (with props text,
idleLabel, copyingLabel, copiedLabel, feedbackDurationMs, onCopy, onCopyError,
onClick) remains exported; after removal, ensure there are no leftover duplicate
exports or references to the old props (variant/onCopySuccess/children) and run
tests to let consumer migration to the new API happen in a separate PR.
---
Duplicate comments:
In `@frontend/src/components/v1/CopyButton.tsx`:
- Around line 95-106: The CopyButton handler can get stuck in "copying" if
user-supplied callbacks throw; update state before awaiting callbacks and guard
callback calls with try/catch to prevent thrown errors from aborting state
updates. Specifically, in the copy handler inside the CopyButton component
setStatus('copied') and call scheduleReset() before awaiting onCopy, and in the
error path setStatus('idle') and setError/setGlobalError before awaiting
onCopyError; additionally wrap await onCopy(...) and await onCopyError(...) in
try/catch (or call them without awaiting) so exceptions don’t prevent the prior
state changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 763cc16b-b125-4d8b-88bf-6355a4ae8d8c
📒 Files selected for processing (5)
frontend/src/components/v1/CopyButton.tsxfrontend/src/components/v1/index.tsfrontend/src/utils/v1/clipboard.tsfrontend/tests/components/v1/CopyButton.test.tsxfrontend/tests/utils/v1/clipboard.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- frontend/tests/components/v1/CopyButton.test.tsx
- frontend/tests/utils/v1/clipboard.test.ts
- frontend/src/components/v1/index.ts
Closes #426
Summary by CodeRabbit