From 871ae2c7339c5ea864a52a8ddecbdb142aff739f Mon Sep 17 00:00:00 2001 From: ghaddaditw <40211818-ghaddaditw@users.noreply.replit.com> Date: Sun, 8 Jun 2025 16:06:07 +0000 Subject: [PATCH] Enhance user experience with animated loading placeholders across the platform Implements animated loading skeletons using React and Tailwind CSS for Chat, Finances, and Tasks pages. Replit-Commit-Author: Agent Replit-Commit-Session-Id: d7e7c4e8-20cb-41c4-9d0e-79f48938fede Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9777c70b-fc38-4831-8d6b-78dfffe041b0/db413c20-caa8-482a-9f55-d510ff0c04b5.jpg --- client/src/components/ui/loading-skeleton.tsx | 203 ++++++++++++++++++ client/src/pages/ChatPage.tsx | 4 +- client/src/pages/FinancesPage.tsx | 43 +++- client/src/pages/TasksPage.tsx | 18 +- 4 files changed, 254 insertions(+), 14 deletions(-) create mode 100644 client/src/components/ui/loading-skeleton.tsx diff --git a/client/src/components/ui/loading-skeleton.tsx b/client/src/components/ui/loading-skeleton.tsx new file mode 100644 index 0000000..637b0c3 --- /dev/null +++ b/client/src/components/ui/loading-skeleton.tsx @@ -0,0 +1,203 @@ +import { cn } from "@/lib/utils" + +interface SkeletonProps { + className?: string +} + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ) +} + +// Card skeleton for general content +function CardSkeleton({ className }: SkeletonProps) { + return ( +
+
+
+ + +
+
+ + + +
+
+
+ ) +} + +// Task item skeleton +function TaskSkeleton({ className }: SkeletonProps) { + return ( +
+ +
+ + +
+
+ + +
+
+ ) +} + +// Financial record skeleton +function FinancialSkeleton({ className }: SkeletonProps) { + return ( +
+
+ +
+ + +
+
+
+ + +
+
+ ) +} + +// Chat message skeleton +function MessageSkeleton({ className, isOwn = false }: SkeletonProps & { isOwn?: boolean }) { + return ( +
+ {!isOwn && } +
+
+ + +
+ +
+
+ ) +} + +// Analytics chart skeleton +function ChartSkeleton({ className }: SkeletonProps) { + return ( +
+
+ + +
+
+ {Array.from({ length: 7 }).map((_, i) => ( + + ))} +
+
+ {Array.from({ length: 7 }).map((_, i) => ( + + ))} +
+
+ ) +} + +// Table skeleton +function TableSkeleton({ rows = 5, columns = 4, className }: SkeletonProps & { rows?: number; columns?: number }) { + return ( +
+ {/* Header */} +
+ {Array.from({ length: columns }).map((_, i) => ( + + ))} +
+ {/* Rows */} + {Array.from({ length: rows }).map((_, rowIndex) => ( +
+ {Array.from({ length: columns }).map((_, colIndex) => ( + + ))} +
+ ))} +
+ ) +} + +// Profile skeleton +function ProfileSkeleton({ className }: SkeletonProps) { + return ( +
+ +
+ + + +
+
+ ) +} + +// List skeleton +function ListSkeleton({ items = 5, className }: SkeletonProps & { items?: number }) { + return ( +
+ {Array.from({ length: items }).map((_, i) => ( +
+ +
+ + +
+
+ ))} +
+ ) +} + +// Page skeleton for full page loading +function PageSkeleton({ className }: SkeletonProps) { + return ( +
+ {/* Header */} +
+ + +
+ + {/* Content grid */} +
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+
+ ) +} + +export { + Skeleton, + CardSkeleton, + TaskSkeleton, + FinancialSkeleton, + MessageSkeleton, + ChartSkeleton, + TableSkeleton, + ProfileSkeleton, + ListSkeleton, + PageSkeleton +} \ No newline at end of file diff --git a/client/src/pages/ChatPage.tsx b/client/src/pages/ChatPage.tsx index 15c8ecf..036cf75 100644 --- a/client/src/pages/ChatPage.tsx +++ b/client/src/pages/ChatPage.tsx @@ -3,13 +3,13 @@ import { useAuth } from '@/hooks/useAuth'; import { Card, CardContent, CardHeader } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { Badge } from '@/components/ui/badge'; +import { Avatar, AvatarFallback } from '@/components/ui/avatar'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { PageHeader } from '@/components/ui/page-header'; +import { MessageSkeleton, ListSkeleton } from '@/components/ui/loading-skeleton'; import { useToast } from '@/hooks/use-toast'; import Sidebar from '@/components/layout/Sidebar'; import { diff --git a/client/src/pages/FinancesPage.tsx b/client/src/pages/FinancesPage.tsx index dd274c1..2c86af8 100644 --- a/client/src/pages/FinancesPage.tsx +++ b/client/src/pages/FinancesPage.tsx @@ -9,8 +9,8 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; import { Badge } from '@/components/ui/badge'; import { PageHeader } from '@/components/ui/page-header'; +import { FinancialSkeleton, CardSkeleton, ChartSkeleton } from '@/components/ui/loading-skeleton'; import { useToast } from '@/hooks/use-toast'; -import { apiRequest } from '@/lib/queryClient'; import Sidebar from '@/components/layout/Sidebar'; import { Plus, TrendingUp, TrendingDown, DollarSign } from 'lucide-react'; @@ -99,14 +99,43 @@ export default function FinancesPage() {
-
-
-
-
- {[...Array(3)].map((_, i) => ( -
+
+ + + {/* Summary cards skeleton */} +
+ {Array.from({ length: 3 }).map((_, i) => ( + ))}
+ + {/* Chart skeleton */} +
+ + + Spending Overview + + + + + + + + + Recent Transactions + + +
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+
+
+
diff --git a/client/src/pages/TasksPage.tsx b/client/src/pages/TasksPage.tsx index dee3ae4..f6b6f42 100644 --- a/client/src/pages/TasksPage.tsx +++ b/client/src/pages/TasksPage.tsx @@ -9,6 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; import { Badge } from '@/components/ui/badge'; import { PageHeader } from '@/components/ui/page-header'; +import { TaskSkeleton, CardSkeleton } from '@/components/ui/loading-skeleton'; import { useToast } from '@/hooks/use-toast'; import { VoiceShortcuts } from '@/components/voice/VoiceShortcuts'; import Sidebar from '@/components/layout/Sidebar'; @@ -170,12 +171,19 @@ export default function TasksPage() {
-
-
-
+
+ +
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
- {[...Array(5)].map((_, i) => ( -
+ {Array.from({ length: 8 }).map((_, i) => ( + ))}