From 11be247901ee10bf74f7f09e5a80718d5e8f3ccd Mon Sep 17 00:00:00 2001 From: Moses Date: Sat, 28 Feb 2026 15:31:14 +0100 Subject: [PATCH] feat: add loading states and skeleton screens - Create SkeletonLoader component - Create SkeletonTable component - Create LoadingSpinner component - Add CSS animations for skeleton pulse, spin, fadeIn - Replace loading spinners with skeleton loaders in Dashboard - Replace loading spinners with skeleton loaders in MyTasks --- frontend/src/components/CalendarView.jsx | 1 + frontend/src/components/Dashboard.jsx | 3 +- frontend/src/components/MyTasks.jsx | 3 +- frontend/src/components/SkeletonLoader.jsx | 99 ++++++++++++++++++++++ frontend/src/index.css | 15 ++++ 5 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/SkeletonLoader.jsx diff --git a/frontend/src/components/CalendarView.jsx b/frontend/src/components/CalendarView.jsx index 3a313e5..2b3c37c 100644 --- a/frontend/src/components/CalendarView.jsx +++ b/frontend/src/components/CalendarView.jsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback } from 'react'; import { useAuth } from '../context/AuthContext'; import { TaskModal } from './TaskModal'; +import { SkeletonLoader } from './SkeletonLoader'; import { toast } from 'react-toastify'; const getWeekStart = (date = new Date()) => { diff --git a/frontend/src/components/Dashboard.jsx b/frontend/src/components/Dashboard.jsx index ec0d5d0..0aeaffe 100644 --- a/frontend/src/components/Dashboard.jsx +++ b/frontend/src/components/Dashboard.jsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback } from 'react'; import { useAuth } from '../context/AuthContext'; import { TaskModal } from './TaskModal'; +import { SkeletonLoader } from './SkeletonLoader'; import { toast } from 'react-toastify'; const getWeekStart = (date = new Date()) => { @@ -330,7 +331,7 @@ const Dashboard = () => {
{loading ? ( -
+ ) : tasks.length === 0 ? (
📋
diff --git a/frontend/src/components/MyTasks.jsx b/frontend/src/components/MyTasks.jsx index ba70730..0cc24fe 100644 --- a/frontend/src/components/MyTasks.jsx +++ b/frontend/src/components/MyTasks.jsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback } from 'react'; import { useAuth } from '../context/AuthContext'; import { TaskModal } from './TaskModal'; +import { SkeletonLoader } from './SkeletonLoader'; import { toast } from 'react-toastify'; const getWeekStart = (date = new Date()) => { @@ -176,7 +177,7 @@ const MyTasks = () => {
{loading ? ( -
+ ) : filteredTasks.length === 0 ? (
📋
diff --git a/frontend/src/components/SkeletonLoader.jsx b/frontend/src/components/SkeletonLoader.jsx new file mode 100644 index 0000000..06052bb --- /dev/null +++ b/frontend/src/components/SkeletonLoader.jsx @@ -0,0 +1,99 @@ +export const SkeletonLoader = ({ count = 3 }) => { + return ( + <> + {Array.from({ length: count }).map((_, i) => ( +
+
+
+
+
+ ))} + + + ); +}; + +export const SkeletonTable = ({ rows = 5, cols = 4 }) => { + return ( +
+
+ {Array.from({ length: cols }).map((_, i) => ( +
+ ))} +
+ {Array.from({ length: rows }).map((_, rowIdx) => ( +
+ {Array.from({ length: cols }).map((_, colIdx) => ( +
+ ))} +
+ ))} + +
+ ); +}; + +export const LoadingSpinner = ({ size = 'medium' }) => { + const sizes = { small: '20px', medium: '40px', large: '60px' }; + return ( +
+ +
+ ); +}; diff --git a/frontend/src/index.css b/frontend/src/index.css index 1193757..100b608 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -51,6 +51,21 @@ box-sizing: border-box; } +@keyframes skeleton-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; background: var(--bg-primary);