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(() => {