168 lines
4.7 KiB
TypeScript
168 lines
4.7 KiB
TypeScript
import React, {
|
|
createContext,
|
|
useContext,
|
|
useState,
|
|
useCallback,
|
|
useEffect,
|
|
useRef,
|
|
} from "react";
|
|
import { useUser, useAuth } from "@clerk/clerk-expo";
|
|
import {
|
|
getRecommendations,
|
|
generateRecommendation,
|
|
generateSelfRecommendation,
|
|
type Recommendation,
|
|
type GenerateRecommendationRequest,
|
|
} from "../api/recommendations";
|
|
import log from "../utils/logger";
|
|
|
|
interface RecommendationsContextValue {
|
|
recommendations: Recommendation[];
|
|
loading: boolean;
|
|
error: Error | null;
|
|
refetchRecommendations: () => Promise<void>;
|
|
generateNewRecommendation: (
|
|
data: GenerateRecommendationRequest,
|
|
) => Promise<Recommendation>;
|
|
generateSelfPlan: () => Promise<Recommendation>;
|
|
clearCache: () => void;
|
|
}
|
|
|
|
const RecommendationsContext = createContext<
|
|
RecommendationsContextValue | undefined
|
|
>(undefined);
|
|
|
|
export function RecommendationsProvider({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode;
|
|
}) {
|
|
const { user } = useUser();
|
|
const { getToken } = useAuth();
|
|
const [recommendations, setRecommendations] = useState<Recommendation[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<Error | null>(null);
|
|
const [lastFetchTime, setLastFetchTime] = useState<number>(0);
|
|
const fetchInProgress = useRef(false);
|
|
|
|
// Cache recommendations for 30 seconds
|
|
const CACHE_DURATION = 30000; // 30 seconds
|
|
|
|
const refetchRecommendations = useCallback(async () => {
|
|
if (!user?.id) return;
|
|
|
|
// Prevent concurrent fetches
|
|
if (fetchInProgress.current) {
|
|
log.debug("Recommendations fetch already in progress, skipping");
|
|
return;
|
|
}
|
|
|
|
// Check if we have recent cached data
|
|
const now = Date.now();
|
|
if (recommendations.length > 0 && now - lastFetchTime < CACHE_DURATION) {
|
|
log.debug("Using cached recommendations", {
|
|
count: recommendations.length,
|
|
age: now - lastFetchTime,
|
|
cacheRemaining: CACHE_DURATION - (now - lastFetchTime),
|
|
});
|
|
return;
|
|
}
|
|
|
|
try {
|
|
fetchInProgress.current = true;
|
|
setLoading(true);
|
|
setError(null);
|
|
log.debug("Fetching fresh recommendations", { userId: user.id });
|
|
|
|
const token = await getToken();
|
|
const data = await getRecommendations(user.id, token);
|
|
|
|
setRecommendations(data);
|
|
setLastFetchTime(now);
|
|
log.debug("Recommendations fetched and cached", { count: data.length });
|
|
} catch (err) {
|
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
log.error("Failed to fetch recommendations", error);
|
|
setError(error);
|
|
} finally {
|
|
setLoading(false);
|
|
fetchInProgress.current = false;
|
|
}
|
|
}, [user?.id, getToken, recommendations.length, lastFetchTime]);
|
|
|
|
const generateNewRecommendation = useCallback(
|
|
async (data: GenerateRecommendationRequest): Promise<Recommendation> => {
|
|
if (!user?.id) throw new Error("User not authenticated");
|
|
|
|
const token = await getToken();
|
|
const newRecommendation = await generateRecommendation(data, token);
|
|
|
|
// Optimistically update cache
|
|
setRecommendations((prev) => [...prev, newRecommendation]);
|
|
setLastFetchTime(Date.now());
|
|
log.debug("Recommendation generated and added to cache", {
|
|
recommendationId: newRecommendation.id,
|
|
});
|
|
|
|
return newRecommendation;
|
|
},
|
|
[user?.id, getToken],
|
|
);
|
|
|
|
const generateSelfPlan = useCallback(async (): Promise<Recommendation> => {
|
|
if (!user?.id) throw new Error("User not authenticated");
|
|
|
|
const token = await getToken();
|
|
const recommendation = await generateSelfRecommendation(token);
|
|
|
|
setRecommendations((prev) => [recommendation, ...prev]);
|
|
setLastFetchTime(Date.now());
|
|
|
|
return recommendation;
|
|
}, [user?.id, getToken]);
|
|
|
|
const clearCache = useCallback(() => {
|
|
setRecommendations([]);
|
|
setLoading(false);
|
|
setLastFetchTime(0);
|
|
setError(null);
|
|
fetchInProgress.current = false;
|
|
log.debug("Recommendations cache cleared");
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
clearCache();
|
|
if (user?.id) {
|
|
log.debug("Recommendations cache reset for user", { userId: user.id });
|
|
} else {
|
|
log.debug("Recommendations cache reset on sign-out");
|
|
}
|
|
}, [user?.id, clearCache]);
|
|
|
|
return (
|
|
<RecommendationsContext.Provider
|
|
value={{
|
|
recommendations,
|
|
loading,
|
|
error,
|
|
refetchRecommendations,
|
|
generateNewRecommendation,
|
|
generateSelfPlan,
|
|
clearCache,
|
|
}}
|
|
>
|
|
{children}
|
|
</RecommendationsContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useRecommendations() {
|
|
const context = useContext(RecommendationsContext);
|
|
if (context === undefined) {
|
|
throw new Error(
|
|
"useRecommendations must be used within a RecommendationsProvider",
|
|
);
|
|
}
|
|
return context;
|
|
}
|