230 lines
6.7 KiB
TypeScript
230 lines
6.7 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { View, Text, FlatList, ActivityIndicator, StyleSheet, RefreshControl } from "react-native";
|
|
import { useAuth } from "@clerk/clerk-expo";
|
|
import { Ionicons } from "@expo/vector-icons";
|
|
import { API_BASE_URL, API_ENDPOINTS } from "../../config/api";
|
|
|
|
interface Recommendation {
|
|
id: string;
|
|
userId: string;
|
|
content: string;
|
|
activityPlan?: string;
|
|
dietPlan?: string;
|
|
status: string;
|
|
createdAt: string;
|
|
}
|
|
|
|
export default function RecommendationsScreen() {
|
|
const { getToken, userId } = useAuth();
|
|
const [recommendations, setRecommendations] = useState<Recommendation[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
|
|
const fetchRecommendations = async () => {
|
|
try {
|
|
if (!userId) {
|
|
console.error('No userId available');
|
|
return;
|
|
}
|
|
|
|
const token = await getToken();
|
|
const headers: Record<string, string> = {
|
|
'Content-Type': 'application/json',
|
|
};
|
|
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
|
|
const url = `${API_BASE_URL}${API_ENDPOINTS.RECOMMENDATIONS}?userId=${userId}`;
|
|
console.log('Fetching recommendations from:', url);
|
|
|
|
const res = await fetch(url, { headers });
|
|
|
|
if (!res.ok) {
|
|
const errorText = await res.text();
|
|
console.error('API Error:', res.status, errorText);
|
|
throw new Error(`Network response was not ok: ${res.status}`);
|
|
}
|
|
|
|
const data = await res.json();
|
|
console.log('Recommendations data:', data);
|
|
setRecommendations(data.recommendations || data || []);
|
|
} catch (e) {
|
|
console.error('Failed to load recommendations', e);
|
|
} finally {
|
|
setLoading(false);
|
|
setRefreshing(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchRecommendations();
|
|
}, []);
|
|
|
|
const onRefresh = () => {
|
|
setRefreshing(true);
|
|
fetchRecommendations();
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<View style={styles.centered}>
|
|
<ActivityIndicator size="large" color="#000" />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<Text style={styles.header}>AI Recommendations</Text>
|
|
|
|
{/* AI Context Info Banner */}
|
|
<View style={styles.infoBanner}>
|
|
<Ionicons name="sparkles" size={20} color="#2563eb" />
|
|
<Text style={styles.infoBannerText}>
|
|
Personalized based on your active fitness goals and progress
|
|
</Text>
|
|
</View>
|
|
|
|
<FlatList
|
|
data={recommendations}
|
|
keyExtractor={(item) => item.id}
|
|
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
|
|
ListEmptyComponent={
|
|
<View style={styles.emptyContainer}>
|
|
<Text style={styles.empty}>No recommendations available yet.</Text>
|
|
<Text style={styles.emptySub}>Pull down to refresh</Text>
|
|
</View>
|
|
}
|
|
renderItem={({ item }) => (
|
|
<View style={styles.card}>
|
|
<View style={styles.cardHeader}>
|
|
<Text style={styles.status}>{item.status.toUpperCase()}</Text>
|
|
<Text style={styles.date}>{new Date(item.createdAt).toLocaleDateString()}</Text>
|
|
</View>
|
|
|
|
<Text style={styles.sectionTitle}>Daily Advice</Text>
|
|
<Text style={styles.content}>{item.content}</Text>
|
|
|
|
{item.activityPlan && (
|
|
<>
|
|
<Text style={styles.sectionTitle}>Activity Plan</Text>
|
|
<Text style={styles.content}>{item.activityPlan}</Text>
|
|
</>
|
|
)}
|
|
|
|
{item.dietPlan && (
|
|
<>
|
|
<Text style={styles.sectionTitle}>Diet Plan</Text>
|
|
<Text style={styles.content}>{item.dietPlan}</Text>
|
|
</>
|
|
)}
|
|
</View>
|
|
)}
|
|
/>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: '#f5f5f5',
|
|
},
|
|
header: {
|
|
fontSize: 28,
|
|
fontWeight: 'bold',
|
|
padding: 20,
|
|
paddingBottom: 12,
|
|
color: '#1a1a1a',
|
|
},
|
|
infoBanner: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
backgroundColor: '#eff6ff',
|
|
marginHorizontal: 16,
|
|
marginBottom: 12,
|
|
padding: 12,
|
|
borderRadius: 8,
|
|
borderLeftWidth: 3,
|
|
borderLeftColor: '#2563eb',
|
|
gap: 8,
|
|
},
|
|
infoBannerText: {
|
|
flex: 1,
|
|
fontSize: 13,
|
|
color: '#1e40af',
|
|
lineHeight: 18,
|
|
},
|
|
centered: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
backgroundColor: '#f5f5f5',
|
|
},
|
|
card: {
|
|
backgroundColor: '#fff',
|
|
padding: 16,
|
|
marginHorizontal: 16,
|
|
marginBottom: 12,
|
|
borderRadius: 12,
|
|
shadowColor: '#000',
|
|
shadowOpacity: 0.1,
|
|
shadowRadius: 4,
|
|
shadowOffset: { width: 0, height: 2 },
|
|
elevation: 3,
|
|
},
|
|
cardHeader: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginBottom: 12,
|
|
paddingBottom: 12,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: '#e0e0e0',
|
|
},
|
|
status: {
|
|
fontSize: 12,
|
|
fontWeight: '600',
|
|
color: '#2e7d32',
|
|
backgroundColor: '#e8f5e9',
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 4,
|
|
borderRadius: 4,
|
|
},
|
|
date: {
|
|
fontSize: 12,
|
|
color: '#666',
|
|
},
|
|
sectionTitle: {
|
|
fontSize: 14,
|
|
fontWeight: '600',
|
|
color: '#1a1a1a',
|
|
marginTop: 12,
|
|
marginBottom: 6,
|
|
},
|
|
content: {
|
|
fontSize: 14,
|
|
color: '#333',
|
|
lineHeight: 20,
|
|
},
|
|
emptyContainer: {
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
paddingVertical: 60,
|
|
},
|
|
empty: {
|
|
textAlign: 'center',
|
|
fontSize: 16,
|
|
color: '#666',
|
|
fontWeight: '500',
|
|
},
|
|
emptySub: {
|
|
textAlign: 'center',
|
|
fontSize: 14,
|
|
color: '#999',
|
|
marginTop: 8,
|
|
},
|
|
});
|