diff --git a/frontend/src/components/common/Skeleton.jsx b/frontend/src/components/common/Skeleton.jsx new file mode 100644 index 0000000..a8fd219 --- /dev/null +++ b/frontend/src/components/common/Skeleton.jsx @@ -0,0 +1,115 @@ +/** + * Skeleton loading components for dashboard + */ + +// Base skeleton with animation +const SkeletonBase = ({ className = '' }) => ( +
+); + +// Skeleton for event cards +export const EventCardSkeleton = () => ( +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+); + +// Skeleton for match cards +export const MatchCardSkeleton = () => ( +
+
+ +
+ + + +
+ + +
+
+ +
+
+); + +// Skeleton for request cards +export const RequestCardSkeleton = () => ( +
+
+ +
+ + + +
+
+ + +
+
+
+); + +// Dashboard skeleton layout +export const DashboardSkeleton = () => ( +
+ {/* Header skeleton */} +
+ + +
+ + {/* Events section */} +
+
+ + +
+
+ + +
+
+ + {/* Matches section */} +
+
+ + +
+
+ + +
+
+ + {/* Requests section */} +
+ +
+ +
+
+
+); + +export default DashboardSkeleton; diff --git a/frontend/src/pages/DashboardPage.jsx b/frontend/src/pages/DashboardPage.jsx index f601462..687ea9a 100644 --- a/frontend/src/pages/DashboardPage.jsx +++ b/frontend/src/pages/DashboardPage.jsx @@ -6,6 +6,7 @@ import { useAuth } from '../contexts/AuthContext'; import { dashboardAPI, matchesAPI } from '../services/api'; import { connectSocket, disconnectSocket, getSocket } from '../services/socket'; import HeatBadges from '../components/heats/HeatBadges'; +import { DashboardSkeleton } from '../components/common/Skeleton'; import { Calendar, MapPin, @@ -139,12 +140,7 @@ const DashboardPage = () => { if (loading) { return ( -
-
- -

Loading dashboard...

-
-
+
); } diff --git a/frontend/src/pages/__tests__/DashboardPage.test.jsx b/frontend/src/pages/__tests__/DashboardPage.test.jsx index d0dadaa..30f260f 100644 --- a/frontend/src/pages/__tests__/DashboardPage.test.jsx +++ b/frontend/src/pages/__tests__/DashboardPage.test.jsx @@ -59,12 +59,14 @@ describe('DashboardPage', () => { }); describe('Loading State', () => { - it('should show loading spinner while fetching data', async () => { + it('should show skeleton loading state while fetching data', async () => { dashboardAPI.getData.mockImplementation(() => new Promise(() => {})); renderWithRouter(); - expect(screen.getByText('Loading dashboard...')).toBeInTheDocument(); + // Skeleton uses animate-pulse class for loading animation + const skeletons = document.querySelectorAll('.animate-pulse'); + expect(skeletons.length).toBeGreaterThan(0); }); }); @@ -410,14 +412,13 @@ describe('DashboardPage', () => { renderWithRouter(); + // Wait for content to load await waitFor(() => { expect(screen.getByText('John Lead')).toBeInTheDocument(); }); - // Find the accept button by title attribute - const acceptButton = document.querySelector('button[title="Accept"]'); - expect(acceptButton).toBeTruthy(); - + // Click accept using fireEvent which is synchronous + const acceptButton = screen.getByRole('button', { name: /accept/i }); fireEvent.click(acceptButton); await waitFor(() => {