Skip to content

Commit 6eed39f

Browse files
authored
fix(frontend): handle dashboard share clipboard failures (JhaSourav07#3037)
## Description Fixes JhaSourav07#3036 ## Pillar - [x] 🛠️ Other (Bug fix, refactoring, docs) ## What changed The dashboard Share button was calling `navigator.clipboard.writeText(window.location.href)` without awaiting the returned promise, then immediately showing `Link copied to clipboard!`. That meant browsers could reject the clipboard write while the UI still reported success. I changed that handler to await the clipboard write, show the success toast only after it resolves, and show an error toast when copying fails. I also added focused `DashboardClient` tests for both outcomes: - successful dashboard link copy - rejected clipboard write due to permission/browser failure Opened as part of GSSoC 2026. ## Visual Preview N/A — this is a dashboard interaction/error-handling fix. ## Validation - [x] `npm run format` - [x] `npm run format:check` - [x] `npm run lint` - [x] `npm run typecheck` - [x] `npm run test -- components/dashboard/DashboardClient.test.tsx` - [x] `npm run test` ## 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` and `npm run lint` locally and resolved all errors. - [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 3dcc4c9 + 710189a commit 6eed39f

2 files changed

Lines changed: 60 additions & 4 deletions

File tree

components/dashboard/DashboardClient.test.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
33
import { describe, expect, it, vi, beforeEach } from 'vitest';
4+
import { toast } from 'sonner';
45
import DashboardClient from './DashboardClient';
56

67
vi.mock('next/navigation', () => ({
@@ -15,6 +16,14 @@ vi.mock('next/navigation', () => ({
1516
}),
1617
}));
1718

19+
vi.mock('sonner', () => ({
20+
toast: {
21+
success: vi.fn(),
22+
error: vi.fn(),
23+
info: vi.fn(),
24+
},
25+
}));
26+
1827
// Mock framer-motion to avoid animation issues in tests
1928
vi.mock('framer-motion', () => ({
2029
motion: {
@@ -205,6 +214,7 @@ const mockPeriod = {
205214
describe('DashboardClient', () => {
206215
beforeEach(() => {
207216
vi.restoreAllMocks();
217+
vi.clearAllMocks();
208218
});
209219

210220
it('renders standard single profile view by default', () => {
@@ -327,6 +337,46 @@ describe('DashboardClient', () => {
327337
const generateLink = screen.getByRole('link', { name: /generate your own/i });
328338
expect(generateLink.getAttribute('href')).toBe('/');
329339
});
340+
341+
it('shows a success toast after the dashboard link is copied', async () => {
342+
const writeText = vi.fn().mockResolvedValue(undefined);
343+
Object.defineProperty(navigator, 'clipboard', {
344+
value: { writeText },
345+
configurable: true,
346+
});
347+
348+
render(
349+
<DashboardClient initialData={mockInitialData} username="Shivangi1515" period={mockPeriod} />
350+
);
351+
352+
fireEvent.click(screen.getByRole('button', { name: /^share$/i }));
353+
354+
await waitFor(() => {
355+
expect(writeText).toHaveBeenCalledWith(window.location.href);
356+
expect(toast.success).toHaveBeenCalledWith('Link copied to clipboard!');
357+
});
358+
});
359+
360+
it('shows an error toast when the dashboard link copy fails', async () => {
361+
const writeText = vi.fn().mockRejectedValue(new Error('Permission denied'));
362+
Object.defineProperty(navigator, 'clipboard', {
363+
value: { writeText },
364+
configurable: true,
365+
});
366+
367+
render(
368+
<DashboardClient initialData={mockInitialData} username="Shivangi1515" period={mockPeriod} />
369+
);
370+
371+
fireEvent.click(screen.getByRole('button', { name: /^share$/i }));
372+
373+
await waitFor(() => {
374+
expect(writeText).toHaveBeenCalledWith(window.location.href);
375+
expect(toast.error).toHaveBeenCalledWith('Failed to copy dashboard link');
376+
});
377+
expect(toast.success).not.toHaveBeenCalledWith('Link copied to clipboard!');
378+
});
379+
330380
// =========================================================================
331381
// ISSUE OBJECTIVE: Verify error is shown when comparing with same username
332382
// =========================================================================

components/dashboard/DashboardClient.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,15 @@ export default function DashboardClient({
477477
}
478478
};
479479

480+
const handleShareDashboard = async () => {
481+
try {
482+
await navigator.clipboard.writeText(window.location.href);
483+
toast.success('Link copied to clipboard!');
484+
} catch {
485+
toast.error('Failed to copy dashboard link');
486+
}
487+
};
488+
480489
// ------------------------------------------------------------
481490
// Compare Mode Statistics Calculations
482491
// ------------------------------------------------------------
@@ -610,10 +619,7 @@ export default function DashboardClient({
610619

611620
<RefreshButton username={username} />
612621
<button
613-
onClick={() => {
614-
navigator.clipboard.writeText(window.location.href);
615-
toast.success('Link copied to clipboard!');
616-
}}
622+
onClick={handleShareDashboard}
617623
className="flex items-center gap-2 rounded-xl border border-black/10 px-4 py-2 text-sm font-semibold hover:bg-gray-100 dark:hover:bg-zinc-800 transition"
618624
>
619625
<Share2 size={16} />

0 commit comments

Comments
 (0)