redesign take 1

This commit is contained in:
dimitar committed 2026-03-11 08:22:48 +01:00
1 parent 1143f8ca02
commit e3a3c3fccf
19 files changed
+2783 -1493

No files matched your search

+230 -252
View File
@@ -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,
},
});