186 lines
4.6 KiB
TypeScript
186 lines
4.6 KiB
TypeScript
import React, {
|
|
createContext,
|
|
useContext,
|
|
useState,
|
|
useCallback,
|
|
useEffect,
|
|
} from "react";
|
|
import { useUser, useAuth } from "@clerk/clerk-expo";
|
|
import {
|
|
getHydrationByDate,
|
|
saveDailyHydration,
|
|
type HydrationEntry,
|
|
} from "../api/hydration";
|
|
import log from "../utils/logger";
|
|
|
|
interface HydrationContextValue {
|
|
hydration: HydrationEntry | null;
|
|
loading: boolean;
|
|
error: Error | null;
|
|
waterGoal: number;
|
|
todayTotal: number;
|
|
percentage: number;
|
|
addWater: (amount: number) => Promise<void>;
|
|
resetToday: () => Promise<void>;
|
|
setWaterGoal: (goal: number) => void;
|
|
refetch: () => Promise<void>;
|
|
}
|
|
|
|
const HydrationContext = createContext<HydrationContextValue | undefined>(
|
|
undefined,
|
|
);
|
|
|
|
export function HydrationProvider({ children }: { children: React.ReactNode }) {
|
|
const { user } = useUser();
|
|
const { getToken } = useAuth();
|
|
const [hydration, setHydration] = useState<HydrationEntry | null>(null);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<Error | null>(null);
|
|
const [waterGoal, setWaterGoal] = useState(2000); // Default goal: 2000ml
|
|
|
|
const today = new Date().toISOString().split("T")[0];
|
|
|
|
const fetchTodayHydration = useCallback(async () => {
|
|
if (!user?.id) return;
|
|
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
const token = await getToken();
|
|
const data = await getHydrationByDate(today, token);
|
|
|
|
if (data) {
|
|
setHydration(data);
|
|
if (data.waterGoal) {
|
|
setWaterGoal(data.waterGoal);
|
|
}
|
|
} else {
|
|
// No data for today yet
|
|
setHydration(null);
|
|
}
|
|
|
|
log.debug("Today's hydration fetched", { data });
|
|
} catch (err) {
|
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
log.error("Failed to fetch hydration", error);
|
|
setError(error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [user?.id, getToken, today]);
|
|
|
|
useEffect(() => {
|
|
fetchTodayHydration();
|
|
}, [fetchTodayHydration]);
|
|
|
|
useEffect(() => {
|
|
setHydration(null);
|
|
setError(null);
|
|
setLoading(false);
|
|
setWaterGoal(2000);
|
|
if (user?.id) {
|
|
log.debug("Hydration state reset for user", { userId: user.id });
|
|
} else {
|
|
log.debug("Hydration state reset on sign-out");
|
|
}
|
|
}, [user?.id]);
|
|
|
|
const addWater = useCallback(
|
|
async (amount: number) => {
|
|
if (!user?.id) return;
|
|
|
|
try {
|
|
const token = await getToken();
|
|
const currentTotal = hydration?.totalWater || 0;
|
|
const newTotal = currentTotal + amount;
|
|
|
|
const entry = await saveDailyHydration(
|
|
{
|
|
date: today,
|
|
totalWater: newTotal,
|
|
waterGoal,
|
|
entries: [
|
|
...(hydration?.entries || []),
|
|
{ amount, time: new Date().toISOString() },
|
|
],
|
|
},
|
|
token,
|
|
);
|
|
|
|
setHydration(entry);
|
|
log.debug("Water added successfully", { amount, newTotal });
|
|
} catch (err) {
|
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
log.error("Failed to add water", error);
|
|
setError(error);
|
|
throw error; // Re-throw so UI can handle it
|
|
}
|
|
},
|
|
[user?.id, getToken, today, hydration, waterGoal],
|
|
);
|
|
|
|
const resetToday = useCallback(async () => {
|
|
if (!user?.id) return;
|
|
|
|
try {
|
|
const token = await getToken();
|
|
|
|
await saveDailyHydration(
|
|
{
|
|
date: today,
|
|
totalWater: 0,
|
|
waterGoal,
|
|
entries: [],
|
|
},
|
|
token,
|
|
);
|
|
|
|
setHydration({
|
|
date: today,
|
|
totalWater: 0,
|
|
waterGoal,
|
|
entries: [],
|
|
});
|
|
|
|
log.debug("Hydration reset for today");
|
|
} catch (err) {
|
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
log.error("Failed to reset hydration", error);
|
|
setError(error);
|
|
throw error;
|
|
}
|
|
}, [user?.id, getToken, today, waterGoal]);
|
|
|
|
const todayTotal = hydration?.totalWater || 0;
|
|
const percentage =
|
|
waterGoal > 0 ? Math.round((todayTotal / waterGoal) * 100) : 0;
|
|
|
|
const value: HydrationContextValue = {
|
|
hydration,
|
|
loading,
|
|
error,
|
|
waterGoal,
|
|
todayTotal,
|
|
percentage,
|
|
addWater,
|
|
resetToday,
|
|
setWaterGoal,
|
|
refetch: fetchTodayHydration,
|
|
};
|
|
|
|
return (
|
|
<HydrationContext.Provider value={value}>
|
|
{children}
|
|
</HydrationContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useHydration() {
|
|
const context = useContext(HydrationContext);
|
|
if (context === undefined) {
|
|
throw new Error("useHydration must be used within a HydrationProvider");
|
|
}
|
|
return context;
|
|
}
|