ai rec refinement
This commit is contained in:
parent
e9ba9e2700
commit
0896dd46d1
Binary file not shown.
@ -28,16 +28,22 @@ export async function GET(request: Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check permissions: Users can view their own, Admins/Trainers can view anyone's
|
// Check permissions: Users can view their own, Admins/Trainers can view anyone's
|
||||||
if (currentUserId !== targetUserId) {
|
|
||||||
const currentUser = await db.getUserById(currentUserId)
|
const currentUser = await db.getUserById(currentUserId)
|
||||||
const isStaff = currentUser?.role === 'admin' || currentUser?.role === 'superAdmin' || currentUser?.role === 'trainer'
|
const isStaff = currentUser?.role === 'admin' || currentUser?.role === 'superAdmin' || currentUser?.role === 'trainer'
|
||||||
|
|
||||||
|
if (currentUserId !== targetUserId) {
|
||||||
if (!isStaff) {
|
if (!isStaff) {
|
||||||
return new NextResponse('Forbidden', { status: 403 })
|
return new NextResponse('Forbidden', { status: 403 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const recommendations = await db.getRecommendationsByUserId(targetUserId)
|
let recommendations = await db.getRecommendationsByUserId(targetUserId)
|
||||||
|
|
||||||
|
// Non-staff users should only see approved recommendations
|
||||||
|
if (!isStaff) {
|
||||||
|
recommendations = recommendations.filter((rec: any) => rec.status === 'approved')
|
||||||
|
}
|
||||||
|
|
||||||
return NextResponse.json(recommendations)
|
return NextResponse.json(recommendations)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching recommendations:', error)
|
console.error('Error fetching recommendations:', error)
|
||||||
|
|||||||
201
apps/mobile/src/app/(tabs)/recommendations.tsx
Normal file
201
apps/mobile/src/app/(tabs)/recommendations.tsx
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { View, Text, FlatList, ActivityIndicator, StyleSheet, RefreshControl } from "react-native";
|
||||||
|
import { useAuth } from "@clerk/clerk-expo";
|
||||||
|
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>
|
||||||
|
<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',
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user