redesign take 1
This commit is contained in:
1 parent
1143f8ca02
commit
e3a3c3fccf
19 files changed
+2783
-1493
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useCallback, useRef } from "react";
|
||||
import React, { useState, useCallback } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
@@ -9,11 +9,15 @@ import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
} from "react-native";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { useUser } from "@clerk/clerk-expo";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import { theme } from "../../styles/theme";
|
||||
import { useTheme } from "../../contexts/ThemeContext";
|
||||
import { MinimalCard } from "../../components/MinimalCard";
|
||||
import { SectionHeader } from "../../components/SectionHeader";
|
||||
import { MinimalButton } from "../../components/MinimalButton";
|
||||
import { Badge } from "../../components/Badge";
|
||||
import { IconContainer } from "../../components/IconContainer";
|
||||
import { useRecommendations } from "../../contexts/RecommendationsContext";
|
||||
import { useNotifications } from "../../contexts/NotificationsContext";
|
||||
import { NotificationsModal } from "../../components/NotificationsModal";
|
||||
@@ -22,6 +26,7 @@ import log from "../../utils/logger";
|
||||
|
||||
export default function RecommendationsScreen() {
|
||||
const { user } = useUser();
|
||||
const { colors, typography } = useTheme();
|
||||
const {
|
||||
recommendations: allRecommendations,
|
||||
loading,
|
||||
@@ -43,7 +48,7 @@ export default function RecommendationsScreen() {
|
||||
setNotificationsVisible(false);
|
||||
};
|
||||
|
||||
// Filter to show only approved recommendations for regular users
|
||||
// Filter to show only approved recommendations
|
||||
const recommendations = allRecommendations.filter(
|
||||
(rec) => rec.status === "approved",
|
||||
);
|
||||
@@ -101,14 +106,14 @@ export default function RecommendationsScreen() {
|
||||
|
||||
if (loading && recommendations.length === 0) {
|
||||
return (
|
||||
<View style={styles.centered}>
|
||||
<ActivityIndicator size="large" color={theme.colors.primary} />
|
||||
<View style={[styles.centered, { backgroundColor: colors.background }]}>
|
||||
<ActivityIndicator size="large" color={colors.primary} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
<NotificationsModal
|
||||
visible={notificationsVisible}
|
||||
onClose={handleCloseNotifications}
|
||||
@@ -119,98 +124,126 @@ export default function RecommendationsScreen() {
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
tintColor={theme.colors.primary}
|
||||
tintColor={colors.primary}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{/* Header */}
|
||||
<LinearGradient
|
||||
colors={theme.gradients.primary}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={styles.header}
|
||||
>
|
||||
<View style={styles.header}>
|
||||
<View>
|
||||
<Text style={styles.headerTitle}>AI Recommendations</Text>
|
||||
<Text style={styles.headerSubtitle}>
|
||||
<Text style={[typography.h1, { color: colors.textPrimary }]}>
|
||||
AI Recommendations
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
typography.body,
|
||||
{ color: colors.textSecondary, marginTop: 4 },
|
||||
]}
|
||||
>
|
||||
Personalized fitness & nutrition plans
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={styles.iconContainer}
|
||||
style={styles.notificationButton}
|
||||
onPress={handleOpenNotifications}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Ionicons name="sparkles" size={32} color="#fff" />
|
||||
{unreadCount > 0 && (
|
||||
<View style={styles.badge}>
|
||||
<Text style={styles.badgeText}>{unreadCount}</Text>
|
||||
</View>
|
||||
)}
|
||||
<IconContainer
|
||||
variant="colored"
|
||||
backgroundColor={`${colors.accent}20`}
|
||||
>
|
||||
<Ionicons name="sparkles" size={24} color={colors.accent} />
|
||||
{unreadCount > 0 && (
|
||||
<View
|
||||
style={[
|
||||
styles.notificationBadge,
|
||||
{ backgroundColor: colors.danger },
|
||||
]}
|
||||
>
|
||||
<Text style={styles.notificationBadgeText}>
|
||||
{unreadCount}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</IconContainer>
|
||||
</TouchableOpacity>
|
||||
</LinearGradient>
|
||||
</View>
|
||||
|
||||
{/* Generate Button */}
|
||||
<View style={styles.actionContainer}>
|
||||
<TouchableOpacity
|
||||
<View style={styles.section}>
|
||||
<MinimalButton
|
||||
title="Generate New Plan"
|
||||
onPress={handleGenerateRecommendation}
|
||||
variant="primary"
|
||||
size="lg"
|
||||
loading={generating}
|
||||
disabled={generating}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={theme.gradients.purple}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 0 }}
|
||||
style={[styles.generateButton, theme.shadows.medium]}
|
||||
>
|
||||
{generating ? (
|
||||
<ActivityIndicator size="small" color="#fff" />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons
|
||||
name="bulb"
|
||||
size={24}
|
||||
color="#fff"
|
||||
style={{ marginRight: 12 }}
|
||||
/>
|
||||
<Text style={styles.generateButtonText}>
|
||||
Generate New Plan
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
textStyle={{ fontSize: 16 }}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Recommendations List */}
|
||||
<View style={styles.section}>
|
||||
<SectionHeader
|
||||
title="Your Plans"
|
||||
subtitle={
|
||||
recommendations.length > 0
|
||||
? `${recommendations.length} active plan${recommendations.length !== 1 ? "s" : ""}`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
||||
{recommendations.length === 0 ? (
|
||||
<View style={styles.emptyState}>
|
||||
<LinearGradient
|
||||
colors={["rgba(139, 92, 246, 0.1)", "rgba(139, 92, 246, 0.05)"]}
|
||||
style={styles.emptyCard}
|
||||
>
|
||||
<Ionicons
|
||||
name="sparkles-outline"
|
||||
size={64}
|
||||
color={theme.colors.purple}
|
||||
/>
|
||||
<Text style={styles.emptyTitle}>No Recommendations Yet</Text>
|
||||
<Text style={styles.emptyText}>
|
||||
<MinimalCard variant="default">
|
||||
<View style={styles.emptyState}>
|
||||
<IconContainer
|
||||
variant="colored"
|
||||
backgroundColor={`${colors.accent}15`}
|
||||
size="lg"
|
||||
>
|
||||
<Ionicons
|
||||
name="sparkles-outline"
|
||||
size={32}
|
||||
color={colors.accent}
|
||||
/>
|
||||
</IconContainer>
|
||||
<Text
|
||||
style={[
|
||||
typography.bodyEmphasis,
|
||||
{ color: colors.textPrimary, marginTop: 16 },
|
||||
]}
|
||||
>
|
||||
No Recommendations Yet
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
typography.body,
|
||||
{
|
||||
color: colors.textSecondary,
|
||||
marginTop: 8,
|
||||
textAlign: "center",
|
||||
},
|
||||
]}
|
||||
>
|
||||
Tap "Generate New Plan" to get personalized AI-powered fitness
|
||||
and nutrition recommendations based on your profile and goals.
|
||||
</Text>
|
||||
</LinearGradient>
|
||||
</View>
|
||||
</View>
|
||||
</MinimalCard>
|
||||
) : (
|
||||
recommendations.map((recommendation) => (
|
||||
<RecommendationCard
|
||||
key={recommendation.id}
|
||||
recommendation={recommendation}
|
||||
/>
|
||||
))
|
||||
<View style={styles.recommendationsList}>
|
||||
{recommendations.map((recommendation) => (
|
||||
<RecommendationCard
|
||||
key={recommendation.id}
|
||||
recommendation={recommendation}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Bottom Spacer */}
|
||||
<View style={{ height: 100 }} />
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
@@ -221,250 +254,195 @@ interface RecommendationCardProps {
|
||||
}
|
||||
|
||||
function RecommendationCard({ recommendation }: RecommendationCardProps) {
|
||||
const { colors, typography } = useTheme();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<View style={styles.card}>
|
||||
<LinearGradient
|
||||
colors={["rgba(255, 255, 255, 1)", "rgba(249, 250, 251, 1)"]}
|
||||
style={[styles.cardContent, theme.shadows.medium]}
|
||||
<MinimalCard variant="elevated">
|
||||
{/* Header */}
|
||||
<TouchableOpacity
|
||||
onPress={() => setExpanded(!expanded)}
|
||||
activeOpacity={0.7}
|
||||
style={styles.cardHeader}
|
||||
>
|
||||
{/* Header */}
|
||||
<View style={styles.cardHeader}>
|
||||
<View style={styles.cardHeaderLeft}>
|
||||
<LinearGradient
|
||||
colors={theme.gradients.success}
|
||||
style={styles.cardIcon}
|
||||
>
|
||||
<Ionicons name="checkmark-circle" size={20} color="#fff" />
|
||||
</LinearGradient>
|
||||
<View>
|
||||
<Text style={styles.cardTitle}>AI Fitness Plan</Text>
|
||||
<Text style={styles.cardDate}>
|
||||
{new Date(recommendation.generatedAt).toLocaleDateString()}
|
||||
<View style={styles.cardHeaderLeft}>
|
||||
<IconContainer
|
||||
variant="colored"
|
||||
backgroundColor={`${colors.success}20`}
|
||||
>
|
||||
<Ionicons
|
||||
name="checkmark-circle"
|
||||
size={20}
|
||||
color={colors.success}
|
||||
/>
|
||||
</IconContainer>
|
||||
<View style={{ marginLeft: 12 }}>
|
||||
<Text style={[typography.h3, { color: colors.textPrimary }]}>
|
||||
AI Fitness Plan
|
||||
</Text>
|
||||
<Text style={[typography.caption, { color: colors.textTertiary }]}>
|
||||
{new Date(recommendation.generatedAt).toLocaleDateString()}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Ionicons
|
||||
name={expanded ? "chevron-up" : "chevron-down"}
|
||||
size={24}
|
||||
color={colors.textTertiary}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Summary */}
|
||||
<View style={styles.cardSummary}>
|
||||
<Text
|
||||
style={[typography.body, { color: colors.textSecondary }]}
|
||||
numberOfLines={expanded ? undefined : 3}
|
||||
>
|
||||
{recommendation.recommendationText}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Expanded Content */}
|
||||
{expanded && (
|
||||
<View style={styles.expandedContent}>
|
||||
{/* Activity Plan */}
|
||||
<View
|
||||
style={[
|
||||
styles.planSection,
|
||||
{ backgroundColor: colors.surfaceElevated },
|
||||
]}
|
||||
>
|
||||
<View style={styles.planHeader}>
|
||||
<Ionicons name="barbell" size={20} color={colors.primary} />
|
||||
<Text
|
||||
style={[
|
||||
typography.h3,
|
||||
{ color: colors.textPrimary, marginLeft: 8 },
|
||||
]}
|
||||
>
|
||||
Activity Plan
|
||||
</Text>
|
||||
</View>
|
||||
<Text
|
||||
style={[
|
||||
typography.body,
|
||||
{ color: colors.textSecondary, marginTop: 8 },
|
||||
]}
|
||||
>
|
||||
{recommendation.activityPlan}
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity onPress={() => setExpanded(!expanded)}>
|
||||
<Ionicons
|
||||
name={expanded ? "chevron-up" : "chevron-down"}
|
||||
size={24}
|
||||
color={theme.colors.gray400}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Summary */}
|
||||
<View style={styles.cardSummary}>
|
||||
<Text
|
||||
style={styles.summaryText}
|
||||
numberOfLines={expanded ? undefined : 3}
|
||||
{/* Diet Plan */}
|
||||
<View
|
||||
style={[
|
||||
styles.planSection,
|
||||
{ backgroundColor: colors.surfaceElevated },
|
||||
]}
|
||||
>
|
||||
{recommendation.recommendationText}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Expanded Content */}
|
||||
{expanded && (
|
||||
<View style={styles.expandedContent}>
|
||||
{/* Activity Plan */}
|
||||
<View style={styles.planSection}>
|
||||
<View style={styles.planHeader}>
|
||||
<Ionicons
|
||||
name="barbell"
|
||||
size={20}
|
||||
color={theme.colors.primary}
|
||||
/>
|
||||
<Text style={styles.planTitle}>Activity Plan</Text>
|
||||
</View>
|
||||
<Text style={styles.planText}>{recommendation.activityPlan}</Text>
|
||||
</View>
|
||||
|
||||
{/* Diet Plan */}
|
||||
<View style={styles.planSection}>
|
||||
<View style={styles.planHeader}>
|
||||
<Ionicons
|
||||
name="restaurant"
|
||||
size={20}
|
||||
color={theme.colors.success}
|
||||
/>
|
||||
<Text style={styles.planTitle}>Diet Plan</Text>
|
||||
</View>
|
||||
<Text style={styles.planText}>{recommendation.dietPlan}</Text>
|
||||
<View style={styles.planHeader}>
|
||||
<Ionicons name="restaurant" size={20} color={colors.success} />
|
||||
<Text
|
||||
style={[
|
||||
typography.h3,
|
||||
{ color: colors.textPrimary, marginLeft: 8 },
|
||||
]}
|
||||
>
|
||||
Diet Plan
|
||||
</Text>
|
||||
</View>
|
||||
<Text
|
||||
style={[
|
||||
typography.body,
|
||||
{ color: colors.textSecondary, marginTop: 8 },
|
||||
]}
|
||||
>
|
||||
{recommendation.dietPlan}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</LinearGradient>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</MinimalCard>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
centered: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
scrollContent: {
|
||||
paddingBottom: 100,
|
||||
paddingBottom: 20,
|
||||
},
|
||||
header: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
padding: 24,
|
||||
alignItems: "flex-start",
|
||||
paddingHorizontal: 24,
|
||||
paddingTop: 60,
|
||||
paddingBottom: 24,
|
||||
marginBottom: 20,
|
||||
borderBottomLeftRadius: theme.borderRadius.xl,
|
||||
borderBottomRightRadius: theme.borderRadius.xl,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: theme.typography.fontSize["3xl"],
|
||||
fontWeight: theme.typography.fontWeight.bold,
|
||||
color: theme.colors.white,
|
||||
notificationButton: {
|
||||
position: "relative",
|
||||
},
|
||||
headerSubtitle: {
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
color: "rgba(255, 255, 255, 0.9)",
|
||||
marginTop: 4,
|
||||
},
|
||||
iconContainer: {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.2)",
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: 32,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
badge: {
|
||||
notificationBadge: {
|
||||
position: "absolute",
|
||||
top: -4,
|
||||
right: -4,
|
||||
backgroundColor: theme.colors.danger,
|
||||
borderRadius: 10,
|
||||
width: 20,
|
||||
height: 20,
|
||||
minWidth: 18,
|
||||
height: 18,
|
||||
borderRadius: 9,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
badgeText: {
|
||||
notificationBadgeText: {
|
||||
color: "#fff",
|
||||
fontSize: 12,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
actionContainer: {
|
||||
paddingHorizontal: 20,
|
||||
marginBottom: 20,
|
||||
},
|
||||
generateButton: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 24,
|
||||
borderRadius: theme.borderRadius.xl,
|
||||
},
|
||||
generateButtonText: {
|
||||
fontSize: theme.typography.fontSize.lg,
|
||||
fontWeight: theme.typography.fontWeight.bold,
|
||||
color: theme.colors.white,
|
||||
fontSize: 11,
|
||||
fontWeight: "700",
|
||||
},
|
||||
section: {
|
||||
paddingHorizontal: 20,
|
||||
paddingHorizontal: 24,
|
||||
marginBottom: 24,
|
||||
},
|
||||
emptyState: {
|
||||
paddingVertical: 40,
|
||||
},
|
||||
emptyCard: {
|
||||
borderRadius: theme.borderRadius["2xl"],
|
||||
padding: 32,
|
||||
alignItems: "center",
|
||||
paddingVertical: 40,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
emptyTitle: {
|
||||
fontSize: theme.typography.fontSize.xl,
|
||||
fontWeight: theme.typography.fontWeight.bold,
|
||||
color: theme.colors.gray700,
|
||||
marginTop: 16,
|
||||
marginBottom: 8,
|
||||
},
|
||||
emptyText: {
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
color: theme.colors.gray500,
|
||||
textAlign: "center",
|
||||
lineHeight: 24,
|
||||
},
|
||||
card: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
cardContent: {
|
||||
borderRadius: theme.borderRadius["2xl"],
|
||||
padding: 20,
|
||||
recommendationsList: {
|
||||
gap: 12,
|
||||
},
|
||||
cardHeader: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: 16,
|
||||
},
|
||||
cardHeaderLeft: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 12,
|
||||
},
|
||||
cardIcon: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
cardTitle: {
|
||||
fontSize: theme.typography.fontSize.lg,
|
||||
fontWeight: theme.typography.fontWeight.bold,
|
||||
color: theme.colors.gray800,
|
||||
},
|
||||
cardDate: {
|
||||
fontSize: theme.typography.fontSize.sm,
|
||||
color: theme.colors.gray500,
|
||||
marginTop: 2,
|
||||
flex: 1,
|
||||
},
|
||||
cardSummary: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
summaryText: {
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
color: theme.colors.gray700,
|
||||
lineHeight: 24,
|
||||
marginTop: 12,
|
||||
paddingTop: 12,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: "rgba(0, 0, 0, 0.05)",
|
||||
},
|
||||
expandedContent: {
|
||||
marginTop: 12,
|
||||
paddingTop: 16,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: theme.colors.gray200,
|
||||
marginTop: 16,
|
||||
gap: 12,
|
||||
},
|
||||
planSection: {
|
||||
marginBottom: 16,
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
},
|
||||
planHeader: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
marginBottom: 8,
|
||||
},
|
||||
planTitle: {
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
fontWeight: theme.typography.fontWeight.bold,
|
||||
color: theme.colors.gray800,
|
||||
},
|
||||
planText: {
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
color: theme.colors.gray600,
|
||||
lineHeight: 22,
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user