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 (
+
+
+
+
+ );
+}