135 lines
3.8 KiB
TypeScript
135 lines
3.8 KiB
TypeScript
import React, { createContext, useContext, useState, useCallback } from "react";
|
|
import { useUser, useAuth } from "@clerk/clerk-expo";
|
|
import { getUserStatistics } from "../api/statistics";
|
|
import type { UserStatisticsResponse } from "../api/types";
|
|
import log from "../utils/logger";
|
|
|
|
interface StatisticsContextValue {
|
|
statistics: UserStatisticsResponse | null;
|
|
loading: boolean;
|
|
error: Error | null;
|
|
refetchStatistics: () => Promise<void>;
|
|
forceRefresh: () => Promise<void>;
|
|
clearCache: () => void;
|
|
}
|
|
|
|
const StatisticsContext = createContext<StatisticsContextValue | undefined>(
|
|
undefined,
|
|
);
|
|
|
|
export function StatisticsProvider({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode;
|
|
}) {
|
|
const { user } = useUser();
|
|
const { getToken } = useAuth();
|
|
const [statistics, setStatistics] = useState<UserStatisticsResponse | null>(
|
|
null,
|
|
);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<Error | null>(null);
|
|
const [lastFetchTime, setLastFetchTime] = useState<number>(0);
|
|
|
|
// Cache statistics for 30 seconds to avoid duplicate calls
|
|
const CACHE_DURATION = 30000; // 30 seconds
|
|
|
|
const refetchStatistics = useCallback(async () => {
|
|
if (!user?.id) return;
|
|
|
|
// Check if we have recent cached data
|
|
const now = Date.now();
|
|
if (statistics && now - lastFetchTime < CACHE_DURATION) {
|
|
log.debug("Using cached statistics", {
|
|
age: now - lastFetchTime,
|
|
cacheRemaining: CACHE_DURATION - (now - lastFetchTime),
|
|
});
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
log.debug("Fetching fresh statistics", { userId: user.id });
|
|
|
|
const token = await getToken();
|
|
const stats = await getUserStatistics(user.id, token);
|
|
|
|
setStatistics(stats);
|
|
setLastFetchTime(now);
|
|
log.debug("Statistics fetched and cached", {
|
|
userId: user.id,
|
|
hasWeeklyTrend: !!stats.weeklyTrend,
|
|
weeklyTrendLength: stats.weeklyTrend?.length || 0,
|
|
weeklyTrendSample: stats.weeklyTrend?.[0],
|
|
stats,
|
|
});
|
|
} catch (err) {
|
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
log.error("Failed to fetch statistics", error);
|
|
setError(error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [user?.id, getToken, statistics, lastFetchTime]);
|
|
|
|
const clearCache = useCallback(() => {
|
|
setStatistics(null);
|
|
setLastFetchTime(0);
|
|
setError(null);
|
|
log.debug("Statistics cache cleared");
|
|
}, []);
|
|
|
|
const forceRefresh = useCallback(async () => {
|
|
if (!user?.id) return;
|
|
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
log.debug("Force fetching statistics", { userId: user.id });
|
|
|
|
const token = await getToken();
|
|
const stats = await getUserStatistics(user.id, token);
|
|
|
|
setStatistics(stats);
|
|
setLastFetchTime(Date.now());
|
|
log.debug("Statistics force fetched and cached", {
|
|
userId: user.id,
|
|
hasWeeklyTrend: !!stats.weeklyTrend,
|
|
weeklyTrendLength: stats.weeklyTrend?.length || 0,
|
|
weeklyTrendSample: stats.weeklyTrend?.[0],
|
|
stats,
|
|
});
|
|
} catch (err) {
|
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
log.error("Failed to force fetch statistics", error);
|
|
setError(error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [user?.id, getToken]);
|
|
|
|
return (
|
|
<StatisticsContext.Provider
|
|
value={{
|
|
statistics,
|
|
loading,
|
|
error,
|
|
refetchStatistics,
|
|
forceRefresh,
|
|
clearCache,
|
|
}}
|
|
>
|
|
{children}
|
|
</StatisticsContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useStatistics() {
|
|
const context = useContext(StatisticsContext);
|
|
if (context === undefined) {
|
|
throw new Error("useStatistics must be used within a StatisticsProvider");
|
|
}
|
|
return context;
|
|
}
|