From 34e8ffe5f226bac61b5c224ac04618341847ab0c Mon Sep 17 00:00:00 2001 From: Mannu Date: Sun, 10 May 2026 05:44:39 +0530 Subject: [PATCH] Add offline queue with localStorage --- src/app/page.tsx | 111 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 100 insertions(+), 11 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index a091522..7f0d20c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -2,6 +2,67 @@ import { useState, useEffect } from "react"; +const OFFLINE_QUEUE_KEY = "tia_offline_queue"; + +export interface OfflineEntry { + id: string; + type: "feed" | "diaper" | "sleep"; + data: { + type: "feed" | "diaper" | "sleep"; + childId: string; + subType: string; + amountMl?: number; + notes?: string; + startedAt?: string; + endedAt?: string; + }; + timestamp: number; +} + +// Get queue from localStorage +export function getOfflineQueue(): OfflineEntry[] { + if (typeof window === "undefined") return []; + try { + const data = localStorage.getItem(OFFLINE_QUEUE_KEY); + return data ? JSON.parse(data) : []; + } catch { + return []; + } +} + +// Add to queue +export function addToOfflineQueue(entry: Omit) { + const queue = getOfflineQueue(); + queue.push({ + ...entry, + id: crypto.randomUUID(), + timestamp: Date.now(), + }); + localStorage.setItem(OFFLINE_QUEUE_KEY, JSON.stringify(queue)); +} + +// Process queue when online +export async function processOfflineQueue() { + const queue = getOfflineQueue(); + if (queue.length === 0) return; + + const failed: OfflineEntry[] = []; + + for (const entry of queue) { + try { + await fetch("/api/logs", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(entry.data), + }); + } catch { + failed.push(entry); + } + } + + localStorage.setItem(OFFLINE_QUEUE_KEY, JSON.stringify(failed)); +} + interface LogModalProps { type: "feed" | "diaper" | "sleep" | null; childId: string; @@ -18,21 +79,31 @@ function LogModal({ type, childId, onClose }: LogModalProps) { const handleSubmit = async () => { setLoading(true); + const data = { + type, + childId, + subType, + amountMl: amountMl ? Number(amountMl) : undefined, + notes: notes || undefined, + }; + try { - await fetch("/api/logs", { + // Try online first + const res = await fetch("/api/logs", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - type, - childId, - subType, - amountMl: amountMl ? Number(amountMl) : undefined, - notes: notes || undefined, - }), + body: JSON.stringify(data), }); + + if (!res.ok && !navigator.onLine) { + // Offline - add to queue + addToOfflineQueue({ type: type as any, data }); + } + onClose(); + } catch { + // Network error - add to queue + addToOfflineQueue({ type: type as any, data }); onClose(); - } catch (err) { - console.error(err); } setLoading(false); }; @@ -118,7 +189,19 @@ function LogModal({ type, childId, onClose }: LogModalProps) { export default function HomePage() { const [modalType, setModalType] = useState<"feed" | "diaper" | "sleep" | null>(null); - const [childId, setChildId] = useState("5ad3b16a-1e0d-45ab-bc91-038397d75d0a"); + const [childId] = useState("5ad3b16a-1e0d-45ab-bc91-038397d75d0a"); + const [pendingCount, setPendingCount] = useState(0); + + // Check queue on mount + useEffect(() => { + const queue = getOfflineQueue(); + setPendingCount(queue.length); + + // Process queue when coming online + const handleOnline = () => processOfflineQueue(); + window.addEventListener("online", handleOnline); + return () => window.removeEventListener("online", handleOnline); + }, []); return (
@@ -128,6 +211,12 @@ export default function HomePage() {

Your baby tracking companion

+ {pendingCount > 0 && ( +
+ {pendingCount} pending log{pendingCount > 1 ? "s" : ""} (offline) +
+ )} +