fixed hydration error

This commit is contained in:
john-overton
2025-02-15 20:48:17 -06:00
parent 6e96be8a82
commit 2ce0ce811a
5 changed files with 161 additions and 108 deletions
+13 -8
View File
@@ -1,19 +1,24 @@
import type { Metadata } from 'next'
import './globals.css'
'use client';
export const metadata: Metadata = {
title: 'Baby Tracker',
description: 'Track your baby\'s activities, milestones, and development',
}
import { useEffect, useState } from 'react';
import './globals.css'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
return (
<html lang="en">
<body>{children}</body>
<html lang="en" suppressHydrationWarning>
<body suppressHydrationWarning>
{mounted ? children : null}
</body>
</html>
)
}
+6
View File
@@ -0,0 +1,6 @@
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Baby Tracker',
description: 'Track your baby\'s activities, milestones, and development',
}
+96 -68
View File
@@ -40,68 +40,100 @@ export default function Home() {
const [activities, setActivities] = useState<ActivityType[]>([]);
const [isLoading, setIsLoading] = useState(true);
// Fetch babies on mount
useEffect(() => {
const fetchBabies = async () => {
const fetchData = async () => {
try {
const response = await fetch('/api/baby');
if (response.ok) {
const data = await response.json();
if (data.success) {
setBabies(data.data);
// If there's only one baby, select it automatically
if (data.data.length === 1) {
setSelectedBabyId(data.data[0].id);
setSelectedBaby(data.data[0]);
}
}
// Fetch babies
const babyResponse = await fetch('/api/baby');
if (!babyResponse.ok) return;
const babyData = await babyResponse.json();
if (!babyData.success) return;
setBabies(babyData.data);
// If there's only one baby, select it automatically
if (babyData.data.length === 1) {
const baby = babyData.data[0];
setSelectedBabyId(baby.id);
setSelectedBaby(baby);
// Fetch activities for the selected baby
const [sleepResponse, feedResponse, diaperResponse] = await Promise.all([
fetch(`/api/sleep-log?babyId=${baby.id}`),
fetch(`/api/feed-log?babyId=${baby.id}`),
fetch(`/api/diaper-log?babyId=${baby.id}`)
]);
const [sleepData, feedData, diaperData] = await Promise.all([
sleepResponse.json(),
feedResponse.json(),
diaperResponse.json()
]);
const allActivities = [
...(sleepData.success ? sleepData.data : []),
...(feedData.success ? feedData.data : []),
...(diaperData.success ? diaperData.data : [])
].map(activity => ({
...activity,
time: activity.time ? new Date(activity.time).toISOString() : undefined,
startTime: activity.startTime ? new Date(activity.startTime).toISOString() : undefined,
endTime: activity.endTime ? new Date(activity.endTime).toISOString() : undefined,
}));
setActivities(allActivities);
}
} catch (error) {
console.error('Error fetching babies:', error);
console.error('Error fetching data:', error);
} finally {
setIsLoading(false);
}
};
fetchBabies();
fetchData();
}, []);
// Fetch activities when selected baby changes
useEffect(() => {
const fetchActivities = async () => {
if (!selectedBaby) return;
const handleBabySelect = async (babyId: string) => {
setIsLoading(true);
setSelectedBabyId(babyId);
const selected = babies.find(b => b.id === babyId);
setSelectedBaby(selected || null);
if (selected) {
try {
// Fetch sleep logs
const sleepResponse = await fetch(`/api/sleep-log?babyId=${selectedBaby.id}`);
const sleepData = await sleepResponse.json();
const sleepLogs = sleepData.success ? sleepData.data : [];
// Fetch feed logs
const feedResponse = await fetch(`/api/feed-log?babyId=${selectedBaby.id}`);
const feedData = await feedResponse.json();
const feedLogs = feedData.success ? feedData.data : [];
// Fetch diaper logs
const diaperResponse = await fetch(`/api/diaper-log?babyId=${selectedBaby.id}`);
const diaperData = await diaperResponse.json();
const diaperLogs = diaperData.success ? diaperData.data : [];
// Combine all activities
const [sleepResponse, feedResponse, diaperResponse] = await Promise.all([
fetch(`/api/sleep-log?babyId=${babyId}`),
fetch(`/api/feed-log?babyId=${babyId}`),
fetch(`/api/diaper-log?babyId=${babyId}`)
]);
const [sleepData, feedData, diaperData] = await Promise.all([
sleepResponse.json(),
feedResponse.json(),
diaperResponse.json()
]);
const allActivities = [
...sleepLogs,
...feedLogs,
...diaperLogs,
];
...(sleepData.success ? sleepData.data : []),
...(feedData.success ? feedData.data : []),
...(diaperData.success ? diaperData.data : [])
].map(activity => ({
...activity,
time: activity.time ? new Date(activity.time).toISOString() : undefined,
startTime: activity.startTime ? new Date(activity.startTime).toISOString() : undefined,
endTime: activity.endTime ? new Date(activity.endTime).toISOString() : undefined,
}));
setActivities(allActivities);
} catch (error) {
console.error('Error fetching activities:', error);
}
};
fetchActivities();
}, [selectedBaby]);
} else {
setActivities([]);
}
setIsLoading(false);
};
// Quick action buttons configuration
const quickActions = [
@@ -126,12 +158,6 @@ export default function Home() {
},
];
const handleBabySelect = (babyId: string) => {
setSelectedBabyId(babyId);
const baby = babies.find(b => b.id === babyId);
setSelectedBaby(baby || null);
};
const handleBabyModalClose = async () => {
setShowBabyModal(false);
// Refresh babies list
@@ -193,25 +219,27 @@ export default function Home() {
</div>
{/* Quick Actions */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{quickActions.map((action, index) => (
<Card
key={index}
className={`p-4 cursor-pointer hover:bg-gray-50 transition-colors ${
action.active ? 'bg-primary text-primary-foreground' : ''
} ${action.disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
onClick={action.disabled ? undefined : action.onClick}
>
<div className="flex flex-col items-center justify-center space-y-2">
{action.icon}
<span className="text-sm font-medium">{action.label}</span>
</div>
</Card>
))}
</div>
{!isLoading && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{quickActions.map((action, index) => (
<Card
key={index}
className={`p-4 cursor-pointer hover:bg-gray-50 transition-colors ${
action.active ? 'bg-primary text-primary-foreground' : ''
} ${action.disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
onClick={action.disabled ? undefined : action.onClick}
>
<div className="flex flex-col items-center justify-center space-y-2">
{action.icon}
<span className="text-sm font-medium">{action.label}</span>
</div>
</Card>
))}
</div>
)}
{/* Timeline */}
{selectedBaby && <Timeline activities={activities} />}
{!isLoading && selectedBaby && <Timeline activities={activities} />}
{/* Modals */}
<BabyModal
+5
View File
@@ -0,0 +1,5 @@
import { metadata } from './metadata'
export default function Template({ children }: { children: React.ReactNode }) {
return children;
}
+41 -32
View File
@@ -55,14 +55,14 @@ const getActivityDescription = (activity: ActivityType) => {
return 'Activity logged';
};
const getActivityTime = (activity: ActivityType) => {
const getActivityTime = (activity: ActivityType): Date => {
if ('time' in activity) {
return activity.time;
return new Date(activity.time);
}
if ('startTime' in activity) {
return activity.startTime;
return new Date(activity.startTime);
}
return new Date();
return new Date(); // This should never happen as all activities should have a timestamp
};
export default function Timeline({ activities }: TimelineProps) {
@@ -71,6 +71,12 @@ export default function Timeline({ activities }: TimelineProps) {
getActivityTime(b).getTime() - getActivityTime(a).getTime()
);
const formatTime = (date: Date) => {
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
return `${hours}:${minutes}`;
};
return (
<div className="space-y-4">
<h2 className="text-xl font-semibold">Recent Activity</h2>
@@ -80,36 +86,39 @@ export default function Timeline({ activities }: TimelineProps) {
No activities logged yet
</Card>
) : (
sortedActivities.map((activity) => (
<Card key={activity.id} className="p-4">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className="p-2 bg-gray-100 rounded-full">
{getActivityIcon(activity)}
</div>
<div>
<p className="font-medium">{getActivityDescription(activity)}</p>
<p className="text-sm text-gray-500">
{new Date(getActivityTime(activity)).toLocaleTimeString()}
</p>
sortedActivities.map((activity) => {
const activityTime = getActivityTime(activity);
return (
<Card key={activity.id} className="p-4">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className="p-2 bg-gray-100 rounded-full">
{getActivityIcon(activity)}
</div>
<div>
<p className="font-medium">{getActivityDescription(activity)}</p>
<p className="text-sm text-gray-500">
{formatTime(activityTime)}
</p>
</div>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="p-2 hover:bg-gray-100 rounded-full">
<MoreVertical className="h-4 w-4" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem className="text-red-600">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="p-2 hover:bg-gray-100 rounded-full">
<MoreVertical className="h-4 w-4" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem className="text-red-600">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</Card>
))
</Card>
);
})
)}
</div>
</div>