Skip to content

Commit 018763e

Browse files
authored
fix(frontend): wait for clipboard write before showing copied state (JhaSourav07#2259)
## Description Fixes JhaSourav07#2255 ## Pillar - [x] 🛠️ Other (Bug fix, refactoring, docs) ## What changed The landing page copy handler was marking the badge markdown as copied immediately after calling `navigator.clipboard.writeText`. Since `writeText` returns a Promise, a rejected clipboard write could still show the `Copied` state and open the success guide. This PR makes the handler wait for the clipboard write to finish before updating the UI. If the browser rejects the write, the page leaves the copy state unset instead of showing a false success. I also added a regression test that mocks `navigator.clipboard.writeText` to reject and verifies the success UI is not shown. ## Visual Preview N/A - this is a copy-state behavior fix. ## Local verification - `npm run test -- app/page.test.tsx` passed - `npm run test -- app/customize/components/ThemeQuickPresets.test.tsx` passed after that test timed out once during the full suite run - `npm run format:check` passed - `npm run lint` passed with existing warnings only - `npm run typecheck` passed ## GSSoC 2026 This issue and PR are raised under GSSoC 2026. Please add the relevant GSSoC/level labels if they do not sync from the issue automatically. ## Checklist before requesting a review: - [x] I have read the `CONTRIBUTING.md` file. - [x] I have tested these changes locally (`localhost:3000/api/streak?user=YOUR_USERNAME`). - [x] I have run `npm run format` / `npm run format:check` and `npm run lint` locally. - [x] My commits follow the Conventional Commits format. - [x] I have updated `README.md` if I added a new theme or URL parameter. - [x] I have starred the repo. - [x] I have made sure that I have only one commit to merge in this PR. - [x] The SVG output matches the CommitPulse quality standard.
2 parents af30c96 + b79c174 commit 018763e

2 files changed

Lines changed: 28 additions & 3 deletions

File tree

app/page.test.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,26 @@ describe('LandingPage', () => {
274274
});
275275
});
276276

277+
it('does not show copied state when clipboard write fails', async () => {
278+
vi.mocked(navigator.clipboard.writeText).mockRejectedValueOnce(new Error('Permission denied'));
279+
280+
render(<LandingPage />);
281+
const input = screen.getByPlaceholderText('Enter GitHub Username') as HTMLInputElement;
282+
fireEvent.change(input, { target: { value: 'jhasourav07' } });
283+
284+
const copyButton = screen.getByText('Copy Link').closest('button');
285+
fireEvent.click(copyButton!);
286+
287+
await waitFor(() => {
288+
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
289+
expect.stringContaining('/api/streak?user=jhasourav07')
290+
);
291+
});
292+
293+
expect(screen.queryByText('Copied')).toBeNull();
294+
expect(screen.queryByText('Your Monolith is Ready - Deploy It in 4 Steps')).toBeNull();
295+
});
296+
277297
it('disables Copy Link button when username is empty', () => {
278298
render(<LandingPage />);
279299

app/page.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,18 @@ export default function LandingPage() {
104104
badgeResult?.username === debouncedUsername && badgeResult?.status === 'loaded';
105105
const badgeError = badgeResult?.username === debouncedUsername && badgeResult?.status === 'error';
106106

107-
const copyToClipboard = () => {
107+
const copyToClipboard = async () => {
108108
if (trimmedUsername.length === 0) return;
109109

110+
try {
111+
await navigator.clipboard.writeText(markdown);
112+
} catch {
113+
setCopied(false);
114+
return;
115+
}
116+
110117
trackUser(trimmedUsername);
111118
addSearch(trimmedUsername);
112-
113-
navigator.clipboard.writeText(markdown);
114119
setCopied(true);
115120
setTimeout(() => {
116121
guideRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });

0 commit comments

Comments
 (0)