From 8276db39a29e6c84bde12f998f0b64e89ccd2ae7 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 1 Oct 2025 20:36:11 +0000 Subject: [PATCH] Add skeleton loading states across all tracking pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace CircularProgress spinners with content-aware skeleton screens - Add FormSkeleton for form loading states (feeding, sleep, diaper pages) - Add ActivityListSkeleton for recent activities loading - Improves perceived performance with layout-matching placeholders 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- maternal-web/app/analytics/page.tsx | 18 +- maternal-web/app/page.tsx | 17 +- maternal-web/app/track/diaper/page.tsx | 18 +- maternal-web/app/track/feeding/page.tsx | 14 +- maternal-web/app/track/sleep/page.tsx | 18 +- .../components/common/LoadingSkeletons.tsx | 316 ++++++++++++++++++ 6 files changed, 372 insertions(+), 29 deletions(-) create mode 100644 maternal-web/components/common/LoadingSkeletons.tsx diff --git a/maternal-web/app/analytics/page.tsx b/maternal-web/app/analytics/page.tsx index 7dce600..7fe42fc 100644 --- a/maternal-web/app/analytics/page.tsx +++ b/maternal-web/app/analytics/page.tsx @@ -18,6 +18,7 @@ import { AppShell } from '@/components/layouts/AppShell/AppShell'; import { ProtectedRoute } from '@/components/common/ProtectedRoute'; import { ErrorBoundary } from '@/components/common/ErrorBoundary'; import { ChartErrorFallback } from '@/components/common/ErrorFallbacks'; +import { StatGridSkeleton, ChartSkeleton } from '@/components/common/LoadingSkeletons'; import { TrendingUp, Hotel, @@ -102,15 +103,14 @@ export default function AnalyticsPage() { return ( - - + + + Analytics & Insights + + + + + diff --git a/maternal-web/app/page.tsx b/maternal-web/app/page.tsx index 4b18070..961d13d 100644 --- a/maternal-web/app/page.tsx +++ b/maternal-web/app/page.tsx @@ -8,6 +8,7 @@ import { EmailVerificationBanner } from '@/components/common/EmailVerificationBa import { ErrorBoundary } from '@/components/common/ErrorBoundary'; import { DataErrorFallback } from '@/components/common/ErrorFallbacks'; import { NetworkStatusIndicator } from '@/components/common/NetworkStatusIndicator'; +import { StatGridSkeleton } from '@/components/common/LoadingSkeletons'; import { Restaurant, Hotel, @@ -147,12 +148,11 @@ export default function HomePage() { isolate fallback={} > - - {loading ? ( - - - - ) : !dailySummary ? ( + {loading ? ( + + ) : ( + + {!dailySummary ? ( {children.length === 0 @@ -198,8 +198,9 @@ export default function HomePage() { - )} - + )} + + )} {/* Next Predicted Activity */} diff --git a/maternal-web/app/track/diaper/page.tsx b/maternal-web/app/track/diaper/page.tsx index 06c4dd1..a942648 100644 --- a/maternal-web/app/track/diaper/page.tsx +++ b/maternal-web/app/track/diaper/page.tsx @@ -27,6 +27,7 @@ import { ToggleButton, FormLabel, } from '@mui/material'; +import { FormSkeleton, ActivityListSkeleton } from '@/components/common/LoadingSkeletons'; import { ArrowBack, Refresh, @@ -305,8 +306,17 @@ export default function DiaperTrackPage() { return ( - - + + + Track Diaper Change + + + + + + Recent Diaper Changes + + @@ -545,9 +555,7 @@ export default function DiaperTrackPage() { {diapersLoading ? ( - - - + ) : recentDiapers.length === 0 ? ( diff --git a/maternal-web/app/track/feeding/page.tsx b/maternal-web/app/track/feeding/page.tsx index 9cd2ba2..ce9a954 100644 --- a/maternal-web/app/track/feeding/page.tsx +++ b/maternal-web/app/track/feeding/page.tsx @@ -49,6 +49,7 @@ import { useAuth } from '@/lib/auth/AuthContext'; import { trackingApi, Activity } from '@/lib/api/tracking'; import { childrenApi, Child } from '@/lib/api/children'; import { VoiceInputButton } from '@/components/voice/VoiceInputButton'; +import { FormSkeleton, ActivityListSkeleton } from '@/components/common/LoadingSkeletons'; import { motion } from 'framer-motion'; import { formatDistanceToNow } from 'date-fns'; @@ -308,8 +309,17 @@ function FeedingTrackPage() { return ( - - + + + Track Feeding + + + + + + Recent Feedings + + diff --git a/maternal-web/app/track/sleep/page.tsx b/maternal-web/app/track/sleep/page.tsx index 891e9e5..2af4c6a 100644 --- a/maternal-web/app/track/sleep/page.tsx +++ b/maternal-web/app/track/sleep/page.tsx @@ -24,6 +24,7 @@ import { Chip, Snackbar, } from '@mui/material'; +import { FormSkeleton, ActivityListSkeleton } from '@/components/common/LoadingSkeletons'; import { ArrowBack, Refresh, @@ -320,8 +321,17 @@ export default function SleepTrackPage() { return ( - - + + + Track Sleep + + + + + + Recent Sleep Activities + + @@ -536,9 +546,7 @@ export default function SleepTrackPage() { {sleepsLoading ? ( - - - + ) : recentSleeps.length === 0 ? ( diff --git a/maternal-web/components/common/LoadingSkeletons.tsx b/maternal-web/components/common/LoadingSkeletons.tsx new file mode 100644 index 0000000..ff62e5f --- /dev/null +++ b/maternal-web/components/common/LoadingSkeletons.tsx @@ -0,0 +1,316 @@ +import React from 'react'; +import { Box, Card, CardContent, Skeleton, Stack, Paper } from '@mui/material'; + +/** + * Skeleton loader for activity cards + */ +export function ActivityCardSkeleton() { + return ( + + + + + + + + + + + + + + ); +} + +/** + * Skeleton loader for activity list + */ +export function ActivityListSkeleton({ count = 3 }: { count?: number }) { + return ( + <> + {Array.from({ length: count }).map((_, index) => ( + + ))} + + ); +} + +/** + * Skeleton loader for stat cards + */ +export function StatCardSkeleton() { + return ( + + + + + + ); +} + +/** + * Skeleton loader for stat grid + */ +export function StatGridSkeleton({ count = 4 }: { count?: number }) { + return ( + + {Array.from({ length: count }).map((_, index) => ( + + ))} + + ); +} + +/** + * Skeleton loader for charts + */ +export function ChartSkeleton({ height = 300 }: { height?: number }) { + return ( + + + + + + + + + + + ); +} + +/** + * Skeleton loader for child profile card + */ +export function ChildProfileSkeleton() { + return ( + + + + + + + + + + + + + + ); +} + +/** + * Skeleton loader for form fields + */ +export function FormSkeleton() { + return ( + + + + + + + + + + + + + + + + ); +} + +/** + * Skeleton loader for table rows + */ +export function TableRowSkeleton({ columns = 4 }: { columns?: number }) { + return ( + + {Array.from({ length: columns }).map((_, index) => ( + + ))} + + ); +} + +/** + * Skeleton loader for table + */ +export function TableSkeleton({ rows = 5, columns = 4 }: { rows?: number; columns?: number }) { + return ( + + {/* Header */} + + {Array.from({ length: columns }).map((_, index) => ( + + ))} + + {/* Rows */} + {Array.from({ length: rows }).map((_, index) => ( + + ))} + + ); +} + +/** + * Skeleton loader for list items + */ +export function ListItemSkeleton() { + return ( + + + + + + + + + ); +} + +/** + * Skeleton loader for list + */ +export function ListSkeleton({ count = 5 }: { count?: number }) { + return ( + + {Array.from({ length: count }).map((_, index) => ( + + ))} + + ); +} + +/** + * Skeleton loader for dashboard summary + */ +export function DashboardSummarySkeleton() { + return ( + + {/* Header */} + + + + + + {/* Stats Grid */} + + + {/* Recent Activities */} + + + + + + {/* Charts */} + + + + + + ); +} + +/** + * Skeleton loader for chat messages + */ +export function ChatMessageSkeleton({ isUser = false }: { isUser?: boolean }) { + return ( + + {!isUser && } + + + + + {isUser && } + + ); +} + +/** + * Skeleton loader for chat conversation + */ +export function ChatSkeleton({ messageCount = 4 }: { messageCount?: number }) { + return ( + + {Array.from({ length: messageCount }).map((_, index) => ( + + ))} + + ); +} + +/** + * Skeleton loader for page content + */ +export function PageSkeleton() { + return ( + + {/* Page header */} + + + + + + {/* Content */} + + + + + + + + + + + + + ); +} + +/** + * Full page loading overlay with spinner + */ +export function LoadingOverlay({ message = 'Loading...' }: { message?: string }) { + return ( + + + + + ); +}