api call optimized
This commit is contained in:
1 parent
97436c6823
commit
612259f020
12 files changed
+727
-177
No files matched your search
@@ -16,70 +16,42 @@ import { GoalProgressCard } from "../../components/GoalProgressCard";
|
||||
import { GoalCreationModal } from "../../components/GoalCreationModal";
|
||||
import { WeeklyProgressChart } from "../../components/WeeklyProgressChart";
|
||||
import { GoalTypeBreakdownChart } from "../../components/GoalTypeBreakdownChart";
|
||||
import { useUser, useAuth } from "@clerk/clerk-expo";
|
||||
import {
|
||||
fitnessGoalsService,
|
||||
type FitnessGoal,
|
||||
type CreateGoalData,
|
||||
} from "../../services/fitnessGoals";
|
||||
import { getUserStatistics } from "../../api/statistics";
|
||||
import type { UserStatisticsResponse } from "../../api/types";
|
||||
import { useUser } from "@clerk/clerk-expo";
|
||||
import type { FitnessGoal, CreateGoalData } from "../../services/fitnessGoals";
|
||||
import { useStatistics } from "../../contexts/StatisticsContext";
|
||||
import { useFitnessGoals } from "../../contexts/FitnessGoalsContext";
|
||||
import { useRecommendations } from "../../contexts/RecommendationsContext";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import * as SecureStore from "expo-secure-store";
|
||||
import log from "../../utils/logger";
|
||||
|
||||
export default function GoalsScreen() {
|
||||
const { user } = useUser();
|
||||
const { getToken } = useAuth();
|
||||
const [goals, setGoals] = useState<FitnessGoal[]>([]);
|
||||
const [statistics, setStatistics] = useState<UserStatisticsResponse | null>(
|
||||
null,
|
||||
);
|
||||
const {
|
||||
statistics,
|
||||
refetchStatistics,
|
||||
clearCache: clearStatsCache,
|
||||
} = useStatistics();
|
||||
const {
|
||||
goals,
|
||||
loading,
|
||||
refetchGoals,
|
||||
createGoal,
|
||||
completeGoal,
|
||||
deleteGoal,
|
||||
clearCache: clearGoalsCache,
|
||||
} = useFitnessGoals();
|
||||
const { clearCache: clearRecommendationsCache } = useRecommendations();
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [showAnalytics, setShowAnalytics] = useState(false);
|
||||
const fabScale = useRef(new Animated.Value(1)).current;
|
||||
|
||||
const loadGoals = useCallback(async () => {
|
||||
if (!user?.id) return;
|
||||
try {
|
||||
const token = await getToken();
|
||||
log.debug("Token obtained", {
|
||||
hasToken: !!token,
|
||||
tokenPreview: token ? token.substring(0, 20) + "..." : "No",
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
// Decode and log token details for debugging
|
||||
if (token) {
|
||||
try {
|
||||
const parts = token.split(".");
|
||||
if (parts.length === 3) {
|
||||
const payload = JSON.parse(atob(parts[1]));
|
||||
log.debug("Token details", {
|
||||
issuer: payload.iss,
|
||||
kid: JSON.parse(atob(parts[0])).kid,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log.warn("Could not decode token");
|
||||
}
|
||||
}
|
||||
|
||||
const loadedGoals = await fitnessGoalsService.getGoals(user.id, token);
|
||||
setGoals(loadedGoals);
|
||||
|
||||
// Load statistics
|
||||
try {
|
||||
const stats = await getUserStatistics(user.id);
|
||||
setStatistics(stats);
|
||||
} catch (error) {
|
||||
log.error("Failed to load statistics", error);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error("Failed to load goals", error);
|
||||
}
|
||||
}, [user?.id, getToken]);
|
||||
const loadData = useCallback(async () => {
|
||||
// Load goals and statistics (both cached)
|
||||
await refetchGoals();
|
||||
await refetchStatistics();
|
||||
}, [refetchGoals, refetchStatistics]);
|
||||
|
||||
const clearClerkCache = async () => {
|
||||
Alert.alert(
|
||||
@@ -110,6 +82,11 @@ export default function GoalsScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all caches
|
||||
clearStatsCache();
|
||||
clearGoalsCache();
|
||||
clearRecommendationsCache();
|
||||
|
||||
Alert.alert(
|
||||
"Success",
|
||||
"Cache cleared! Please sign out and sign back in.",
|
||||
@@ -126,37 +103,31 @@ export default function GoalsScreen() {
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
loadGoals();
|
||||
}, [loadGoals]),
|
||||
loadData();
|
||||
}, [loadData]),
|
||||
);
|
||||
|
||||
const onRefresh = async () => {
|
||||
setRefreshing(true);
|
||||
await loadGoals();
|
||||
await loadData();
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
const handleCreateGoal = async (newGoal: CreateGoalData) => {
|
||||
const token = await getToken();
|
||||
await fitnessGoalsService.createGoal(newGoal, token);
|
||||
await loadGoals();
|
||||
await createGoal(newGoal);
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
|
||||
const handleCompleteGoal = async (goal: FitnessGoal) => {
|
||||
const token = await getToken();
|
||||
await fitnessGoalsService.completeGoal(goal.id, token);
|
||||
await loadGoals();
|
||||
await completeGoal(goal.id);
|
||||
};
|
||||
|
||||
const handleDeleteGoal = async (goalId: string) => {
|
||||
const token = await getToken();
|
||||
await fitnessGoalsService.deleteGoal(goalId, token);
|
||||
await loadGoals();
|
||||
await deleteGoal(goalId);
|
||||
};
|
||||
|
||||
const activeGoals = goals.filter((g) => g.status === "active");
|
||||
const completedGoals = goals.filter((g) => g.status === "completed");
|
||||
const activeGoals = goals?.filter((g) => g.status === "active") || [];
|
||||
const completedGoals = goals?.filter((g) => g.status === "completed") || [];
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
@@ -188,7 +159,7 @@ export default function GoalsScreen() {
|
||||
</LinearGradient>
|
||||
|
||||
{/* Stats Summary */}
|
||||
{goals.length > 0 && (
|
||||
{goals && goals.length > 0 && (
|
||||
<View style={styles.statsContainer}>
|
||||
<View style={styles.statCard}>
|
||||
<Text style={styles.statValue}>{activeGoals.length}</Text>
|
||||
|
||||
@@ -14,46 +14,35 @@ import { Ionicons } from "@expo/vector-icons";
|
||||
import { useUser } from "@clerk/clerk-expo";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import { theme } from "../../styles/theme";
|
||||
import {
|
||||
getRecommendations,
|
||||
generateRecommendation,
|
||||
type Recommendation,
|
||||
} from "../../api/recommendations";
|
||||
import { useRecommendations } from "../../contexts/RecommendationsContext";
|
||||
import type { Recommendation } from "../../api/recommendations";
|
||||
import log from "../../utils/logger";
|
||||
|
||||
export default function RecommendationsScreen() {
|
||||
const { user } = useUser();
|
||||
const [recommendations, setRecommendations] = useState<Recommendation[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const {
|
||||
recommendations: allRecommendations,
|
||||
loading,
|
||||
refetchRecommendations,
|
||||
generateNewRecommendation,
|
||||
} = useRecommendations();
|
||||
const [generating, setGenerating] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
const loadRecommendations = useCallback(async () => {
|
||||
if (!user?.id) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await getRecommendations(user.id);
|
||||
// Filter to show only approved recommendations for regular users
|
||||
const approved = data.filter((rec) => rec.status === "approved");
|
||||
setRecommendations(approved);
|
||||
} catch (error) {
|
||||
log.error("Failed to load recommendations", error);
|
||||
Alert.alert("Error", "Failed to load recommendations");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [user?.id]);
|
||||
// Filter to show only approved recommendations for regular users
|
||||
const recommendations = allRecommendations.filter(
|
||||
(rec) => rec.status === "approved",
|
||||
);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
loadRecommendations();
|
||||
}, [loadRecommendations]),
|
||||
refetchRecommendations();
|
||||
}, [refetchRecommendations]),
|
||||
);
|
||||
|
||||
const onRefresh = async () => {
|
||||
setRefreshing(true);
|
||||
await loadRecommendations();
|
||||
await refetchRecommendations();
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
@@ -70,7 +59,7 @@ export default function RecommendationsScreen() {
|
||||
onPress: async () => {
|
||||
try {
|
||||
setGenerating(true);
|
||||
await generateRecommendation({
|
||||
await generateNewRecommendation({
|
||||
userId: user.id,
|
||||
modelProvider: "openai",
|
||||
useExternalModel: true,
|
||||
@@ -79,7 +68,7 @@ export default function RecommendationsScreen() {
|
||||
"Success",
|
||||
"AI recommendation generated! It will appear here once approved by your trainer.",
|
||||
);
|
||||
await loadRecommendations();
|
||||
await refetchRecommendations();
|
||||
} catch (error) {
|
||||
log.error("Failed to generate recommendation", error);
|
||||
Alert.alert(
|
||||
|
||||
@@ -4,6 +4,9 @@ import * as SecureStore from "expo-secure-store";
|
||||
import { View, Text } from "react-native";
|
||||
import { useEffect, useState } from "react";
|
||||
import { validateEnv } from "../utils/env";
|
||||
import { StatisticsProvider } from "../contexts/StatisticsContext";
|
||||
import { FitnessGoalsProvider } from "../contexts/FitnessGoalsContext";
|
||||
import { RecommendationsProvider } from "../contexts/RecommendationsContext";
|
||||
import log from "../utils/logger";
|
||||
|
||||
// Validate environment variables on app startup
|
||||
@@ -150,11 +153,17 @@ export default function RootLayout() {
|
||||
return (
|
||||
<ClerkProvider tokenCache={tokenCache} publishableKey={publishableKey}>
|
||||
<ClerkLoaded>
|
||||
<Stack>
|
||||
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="welcome" options={{ headerShown: false }} />
|
||||
</Stack>
|
||||
<StatisticsProvider>
|
||||
<FitnessGoalsProvider>
|
||||
<RecommendationsProvider>
|
||||
<Stack>
|
||||
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="welcome" options={{ headerShown: false }} />
|
||||
</Stack>
|
||||
</RecommendationsProvider>
|
||||
</FitnessGoalsProvider>
|
||||
</StatisticsProvider>
|
||||
</ClerkLoaded>
|
||||
</ClerkProvider>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user