redesign completed

alot of work ahead
This commit is contained in:
dimitar committed 2025-11-26 02:31:46 +01:00
1 parent 73d2c4c1ed
commit febcdc111e
18 files changed
+1591 -845

No files matched your search

+22 -38
View File
@@ -1,7 +1,7 @@
import { Tabs, useRouter, useSegments } from "expo-router";
import { Ionicons } from "@expo/vector-icons";
import { useAuth } from "@clerk/clerk-expo";
import { useEffect } from "react";
import { CustomTabBar } from "../../components/CustomTabBar";
export default function TabLayout() {
const { isSignedIn, isLoaded } = useAuth();
@@ -25,56 +25,40 @@ export default function TabLayout() {
return (
<Tabs
tabBar={(props) => <CustomTabBar {...props} />}
screenOptions={{
tabBarActiveTintColor: "#2563eb",
tabBarInactiveTintColor: "#6b7280",
headerShown: true,
headerStyle: {
backgroundColor: "#fff",
},
headerTitleStyle: {
fontWeight: "600",
},
headerShown: false, // We'll use custom headers in screens or layout
tabBarShowLabel: false,
}}
>
<Tabs.Screen
name="index"
options={{
title: "Home",
headerTitle: "FitAI",
tabBarIcon: ({ color, size }) => (
<Ionicons name="home" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="attendance"
options={{
title: "Attendance",
headerTitle: "Attendance",
tabBarIcon: ({ color, size }) => (
<Ionicons name="calendar" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="profile"
options={{
title: "Profile",
headerTitle: "My Profile",
tabBarIcon: ({ color, size }) => (
<Ionicons name="person" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="goals"
options={{
title: "Goals",
headerTitle: "Fitness Goals",
tabBarIcon: ({ color, size }) => (
<Ionicons name="trophy" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="recommendations"
options={{
title: "AI",
}}
/>
<Tabs.Screen
name="attendance"
options={{
title: "Attendance",
}}
/>
<Tabs.Screen
name="profile"
options={{
title: "Profile",
}}
/>
</Tabs>
+55 -91
View File
@@ -1,117 +1,81 @@
import { useState, useEffect, useRef } from "react";
import {
View,
Text,
StyleSheet,
ScrollView,
TouchableOpacity,
ActivityIndicator,
RefreshControl,
Alert,
Animated,
} from "react-native";
import { useAuth } from "@clerk/clerk-expo";
import { Ionicons } from "@expo/vector-icons";
import { LinearGradient } from "expo-linear-gradient";
import { fitnessGoalsService, type FitnessGoal, type CreateGoalData } from "../../services/fitnessGoals";
import { GoalProgressCard } from "../../components/GoalProgressCard";
import { GoalCreationModal } from "../../components/GoalCreationModal";
import { theme } from "../../styles/theme";
import React, { useState, useCallback, useRef } from 'react';
import { View, Text, StyleSheet, ScrollView, RefreshControl, TouchableOpacity, Animated, Alert } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import { theme } from '../../styles/theme';
import { GoalProgressCard } from '../../components/GoalProgressCard';
import { GoalCreationModal } from '../../components/GoalCreationModal';
import { useUser, useAuth } from "@clerk/clerk-expo";
import { fitnessGoalsService, type FitnessGoal, type CreateGoalData } from '../../services/fitnessGoals';
import { useFocusEffect } from 'expo-router';
export default function GoalsScreen() {
const { userId, getToken } = useAuth();
const { user } = useUser();
const { getToken } = useAuth();
const [goals, setGoals] = useState<FitnessGoal[]>([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [showCreateModal, setShowCreateModal] = useState(false);
const [isModalVisible, setIsModalVisible] = useState(false);
const fabScale = useRef(new Animated.Value(1)).current;
const fetchGoals = async () => {
if (!userId) return;
const loadGoals = useCallback(async () => {
if (!user?.id) return;
try {
const token = await getToken();
const data = await fitnessGoalsService.getGoals(userId, token);
setGoals(data);
const loadedGoals = await fitnessGoalsService.getGoals(user.id, token);
setGoals(loadedGoals);
} catch (error) {
console.error("Error fetching fitness goals:", error);
Alert.alert("Error", "Failed to load goals. Please try again.");
} finally {
setLoading(false);
setRefreshing(false);
console.error('Error loading goals:', error);
}
};
}, [user?.id]); // Removed getToken from dependencies
useEffect(() => {
fetchGoals();
}, [userId]);
useFocusEffect(
useCallback(() => {
loadGoals();
}, [loadGoals])
);
const onRefresh = () => {
const onRefresh = async () => {
setRefreshing(true);
fetchGoals();
await loadGoals();
setRefreshing(false);
};
const handleCreateGoal = async (goalData: CreateGoalData) => {
try {
const token = await getToken();
const newGoal = await fitnessGoalsService.createGoal(goalData, token);
setGoals(prev => [newGoal, ...prev]);
Alert.alert("Success", "Goal created successfully!");
} catch (error) {
console.error("Error creating goal:", error);
throw error;
}
const handleCreateGoal = async (newGoal: CreateGoalData) => {
const token = await getToken();
await fitnessGoalsService.createGoal(newGoal, token);
await loadGoals();
setIsModalVisible(false);
};
const handleCompleteGoal = async (goalId: string) => {
try {
const token = await getToken();
const updatedGoal = await fitnessGoalsService.completeGoal(goalId, token);
setGoals(prev => prev.map(g => g.id === goalId ? updatedGoal : g));
Alert.alert("Success", "Goal completed! 🎉");
} catch (error) {
console.error("Error completing goal:", error);
Alert.alert("Error", "Failed to complete goal. Please try again.");
}
const handleCompleteGoal = async (goal: FitnessGoal) => {
const token = await getToken();
await fitnessGoalsService.completeGoal(goal.id, token);
await loadGoals();
};
const handleDeleteGoal = async (goalId: string) => {
try {
const token = await getToken();
await fitnessGoalsService.deleteGoal(goalId, token);
setGoals(prev => prev.filter(g => g.id !== goalId));
Alert.alert("Success", "Goal deleted");
} catch (error) {
console.error("Error deleting goal:", error);
Alert.alert("Error", "Failed to delete goal. Please try again.");
}
const token = await getToken();
await fitnessGoalsService.deleteGoal(goalId, token);
await loadGoals();
};
const activeGoals = goals.filter(g => g.status === 'active');
const completedGoals = goals.filter(g => g.status === 'completed');
if (loading && !refreshing) {
return (
<View style={styles.center}>
<ActivityIndicator size="large" color="#2563eb" />
</View>
);
}
return (
<View style={styles.container}>
<ScrollView
contentContainerStyle={styles.scrollContent}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={theme.colors.primary} />
}
>
<LinearGradient
colors={theme.gradients.primary}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.header}
>
<View>
<Text style={styles.headerTitle}>My Fitness Goals</Text>
<Text style={styles.headerTitle}>Fitness Goals</Text>
<Text style={styles.headerSubtitle}>
Track your fitness journey progress
</Text>
@@ -133,7 +97,7 @@ export default function GoalsScreen() {
<Text style={styles.statValue}>
{activeGoals.length > 0
? Math.round(
activeGoals.reduce((sum, g) => sum + g.progress, 0) /
activeGoals.reduce((sum, g) => sum + (g.progress || 0), 0) /
activeGoals.length
)
: 0}%
@@ -161,7 +125,7 @@ export default function GoalsScreen() {
<GoalProgressCard
key={goal.id}
goal={goal}
onComplete={() => handleCompleteGoal(goal.id)}
onComplete={() => handleCompleteGoal(goal)}
onDelete={() => handleDeleteGoal(goal.id)}
/>
))
@@ -188,9 +152,9 @@ export default function GoalsScreen() {
</ScrollView>
{/* Floating Action Button */}
<Animated.View style={{ transform: [{ scale: fabScale }] }}>
<Animated.View style={[styles.fabContainer, { transform: [{ scale: fabScale }] }]}>
<TouchableOpacity
onPress={() => setShowCreateModal(true)}
onPress={() => setIsModalVisible(true)}
onPressIn={() => {
Animated.spring(fabScale, {
toValue: 0.9,
@@ -222,8 +186,8 @@ export default function GoalsScreen() {
{/* Create Goal Modal */}
<GoalCreationModal
visible={showCreateModal}
onClose={() => setShowCreateModal(false)}
visible={isModalVisible}
onClose={() => setIsModalVisible(false)}
onSubmit={handleCreateGoal}
/>
</View>
@@ -235,10 +199,8 @@ const styles = StyleSheet.create({
flex: 1,
backgroundColor: theme.colors.background,
},
center: {
flex: 1,
justifyContent: "center",
alignItems: "center",
scrollContent: {
paddingBottom: 20,
},
header: {
padding: 24,
@@ -268,7 +230,7 @@ const styles = StyleSheet.create({
backgroundColor: theme.colors.white,
padding: 16,
borderRadius: theme.borderRadius.xl,
alignItems: "center",
alignItems: 'center',
...theme.shadows.medium,
borderWidth: 1,
borderColor: "rgba(59, 130, 246, 0.1)",
@@ -312,10 +274,12 @@ const styles = StyleSheet.create({
footer: {
height: 100,
},
fab: {
fabContainer: {
position: "absolute",
right: 20,
bottom: 20,
bottom: 110, // Adjusted for tab bar height
},
fab: {
width: 64,
height: 64,
borderRadius: 32,
+179 -357
View File
@@ -1,239 +1,118 @@
import React, { useEffect, useRef } from "react";
import {
View,
Text,
StyleSheet,
ScrollView,
TouchableOpacity,
Animated,
} from "react-native";
import { View, Text, StyleSheet, ScrollView, RefreshControl, Image } from "react-native";
import { useUser } from "@clerk/clerk-expo";
import { Ionicons } from "@expo/vector-icons";
import { useRouter } from "expo-router";
import { LinearGradient } from "expo-linear-gradient";
import { useState, useCallback } from "react";
import { theme } from "../../styles/theme";
import { ActivityWidget } from "../../components/ActivityWidget";
import { QuickActionGrid } from "../../components/QuickActionGrid";
import { Ionicons } from "@expo/vector-icons";
export default function HomeScreen() {
const { user, isLoaded } = useUser();
const router = useRouter();
const fadeAnim = useRef(new Animated.Value(0)).current;
const { user } = useUser();
const [refreshing, setRefreshing] = useState(false);
useEffect(() => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}).start();
const onRefresh = useCallback(() => {
setRefreshing(true);
setTimeout(() => {
setRefreshing(false);
}, 2000);
}, []);
if (!isLoaded || !user) {
return (
<View style={styles.container}>
<Text>Loading...</Text>
</View>
);
}
const firstName = user.firstName || "User";
const greeting = getGreeting();
const getGreeting = () => {
const hour = new Date().getHours();
if (hour < 12) return "Good Morning";
if (hour < 18) return "Good Afternoon";
return "Good Evening";
};
return (
<ScrollView style={styles.container}>
<Animated.View style={[styles.content, { opacity: fadeAnim }]}>
{/* Gradient Header */}
<LinearGradient
colors={theme.gradients.primary}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.header}
>
<Text style={styles.greeting}>{greeting}!</Text>
<Text style={styles.name}>{firstName}</Text>
</LinearGradient>
{/* Quick Stats with Glassmorphism */}
<View style={styles.statsContainer}>
<LinearGradient
colors={['rgba(59, 130, 246, 0.1)', 'rgba(139, 92, 246, 0.1)']}
style={[styles.statCard, theme.shadows.medium]}
>
<View style={styles.statIconContainer}>
<LinearGradient
colors={theme.gradients.primary}
style={styles.statIconGradient}
>
<Ionicons name="calendar-outline" size={24} color="#fff" />
</LinearGradient>
</View>
<Text style={styles.statValue}>0</Text>
<Text style={styles.statLabel}>This Month</Text>
</LinearGradient>
<LinearGradient
colors={['rgba(239, 68, 68, 0.1)', 'rgba(236, 72, 153, 0.1)']}
style={[styles.statCard, theme.shadows.medium]}
>
<View style={styles.statIconContainer}>
<LinearGradient
colors={theme.gradients.danger}
style={styles.statIconGradient}
>
<Ionicons name="flame-outline" size={24} color="#fff" />
</LinearGradient>
</View>
<Text style={styles.statValue}>0</Text>
<Text style={styles.statLabel}>Day Streak</Text>
</LinearGradient>
<LinearGradient
colors={['rgba(245, 158, 11, 0.1)', 'rgba(217, 119, 6, 0.1)']}
style={[styles.statCard, theme.shadows.medium]}
>
<View style={styles.statIconContainer}>
<LinearGradient
colors={theme.gradients.warning}
style={styles.statIconGradient}
>
<Ionicons name="trophy-outline" size={24} color="#fff" />
</LinearGradient>
</View>
<Text style={styles.statValue}>0</Text>
<Text style={styles.statLabel}>Total Visits</Text>
</LinearGradient>
</View>
{/* Quick Actions */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Quick Actions</Text>
<TouchableOpacity
style={[styles.actionButton, theme.shadows.medium]}
onPress={() => router.push("/fitness-profile")}
activeOpacity={0.7}
>
<LinearGradient
colors={['rgba(236, 72, 153, 0.15)', 'rgba(236, 72, 153, 0.05)']}
style={styles.actionIcon}
>
<Ionicons name="fitness-outline" size={24} color="#ec4899" />
</LinearGradient>
<View style={styles.actionContent}>
<Text style={styles.actionTitle}>Fitness Profile</Text>
<Text style={styles.actionSubtitle}>
Manage your fitness information
</Text>
</View>
<Ionicons name="chevron-forward" size={20} color="#9ca3af" />
</TouchableOpacity>
<TouchableOpacity
style={[styles.actionButton, theme.shadows.medium]}
activeOpacity={0.7}
>
<LinearGradient
colors={['rgba(59, 130, 246, 0.15)', 'rgba(59, 130, 246, 0.05)']}
style={styles.actionIcon}
>
<Ionicons name="log-in-outline" size={24} color="#2563eb" />
</LinearGradient>
<View style={styles.actionContent}>
<Text style={styles.actionTitle}>Check In</Text>
<Text style={styles.actionSubtitle}>
Start your workout session
</Text>
</View>
<Ionicons name="chevron-forward" size={20} color="#9ca3af" />
</TouchableOpacity>
<TouchableOpacity
style={[styles.actionButton, theme.shadows.medium]}
activeOpacity={0.7}
>
<LinearGradient
colors={['rgba(16, 185, 129, 0.15)', 'rgba(16, 185, 129, 0.05)']}
style={styles.actionIcon}
>
<Ionicons name="calendar-outline" size={24} color="#10b981" />
</LinearGradient>
<View style={styles.actionContent}>
<Text style={styles.actionTitle}>View Schedule</Text>
<Text style={styles.actionSubtitle}>
Check your upcoming classes
</Text>
</View>
<Ionicons name="chevron-forward" size={20} color="#9ca3af" />
</TouchableOpacity>
<TouchableOpacity
style={[styles.actionButton, theme.shadows.medium]}
activeOpacity={0.7}
>
<LinearGradient
colors={['rgba(139, 92, 246, 0.15)', 'rgba(139, 92, 246, 0.05)']}
style={styles.actionIcon}
>
<Ionicons name="card-outline" size={24} color="#8b5cf6" />
</LinearGradient>
<View style={styles.actionContent}>
<Text style={styles.actionTitle}>Payments</Text>
<Text style={styles.actionSubtitle}>View payment history</Text>
</View>
<Ionicons name="chevron-forward" size={20} color="#9ca3af" />
</TouchableOpacity>
</View>
{/* Membership Info with Gradient */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Membership</Text>
<LinearGradient
colors={theme.gradients.primary}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={[styles.membershipCard, theme.shadows.strong]}
>
<View style={styles.membershipHeader}>
<Text style={styles.membershipType}>Basic Plan</Text>
<View style={styles.statusBadge}>
<Text style={styles.statusText}>Active</Text>
<View style={styles.container}>
<ScrollView
contentContainerStyle={styles.scrollContent}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={theme.colors.primary} />
}
>
{/* Header Section */}
<View style={styles.header}>
<View>
<Text style={styles.greeting}>{getGreeting()},</Text>
<Text style={styles.name}>{user?.firstName || "Athlete"}</Text>
</View>
<View style={styles.avatarContainer}>
{user?.imageUrl ? (
<Image source={{ uri: user.imageUrl }} style={styles.avatar} />
) : (
<View style={styles.placeholderAvatar}>
<Ionicons name="person" size={24} color="#fff" />
</View>
</View>
<Text style={styles.membershipEmail}>
{user.primaryEmailAddress?.emailAddress}
</Text>
<Text style={styles.membershipDate}>
Member since {new Date(user.createdAt!).toLocaleDateString()}
</Text>
</LinearGradient>
</View>
{/* Recent Activity */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Recent Activity</Text>
<View style={[styles.emptyState, theme.shadows.medium]}>
<View style={styles.emptyIconContainer}>
<LinearGradient
colors={['rgba(209, 213, 219, 0.3)', 'rgba(209, 213, 219, 0.1)']}
style={styles.emptyIconGradient}
>
<Ionicons name="barbell-outline" size={48} color="#9ca3af" />
</LinearGradient>
</View>
<Text style={styles.emptyStateText}>No recent activity</Text>
<Text style={styles.emptyStateSubtext}>
Check in to start tracking your workouts
</Text>
)}
</View>
</View>
</Animated.View>
</ScrollView>
);
}
function getGreeting(): string {
const hour = new Date().getHours();
if (hour < 12) return "Good morning";
if (hour < 18) return "Good afternoon";
return "Good evening";
{/* Activity Widget */}
<ActivityWidget
steps={8432}
calories={640}
duration={45}
/>
{/* Quick Actions */}
<QuickActionGrid />
{/* Recent Activity Section */}
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>Recent Activity</Text>
<Text style={styles.seeAll}>See All</Text>
</View>
<View style={styles.activityCard}>
<LinearGradient
colors={['rgba(255, 255, 255, 0.8)', 'rgba(255, 255, 255, 0.5)']}
style={[styles.recentItem, theme.shadows.subtle]}
>
<View style={styles.recentIconContainer}>
<LinearGradient
colors={theme.gradients.primary}
style={styles.recentIcon}
>
<Ionicons name="barbell" size={20} color="#fff" />
</LinearGradient>
</View>
<View style={styles.recentInfo}>
<Text style={styles.recentTitle}>Upper Body Power</Text>
<Text style={styles.recentSubtitle}>Today, 10:00 AM</Text>
</View>
<Text style={styles.recentValue}>45m</Text>
</LinearGradient>
<LinearGradient
colors={['rgba(255, 255, 255, 0.8)', 'rgba(255, 255, 255, 0.5)']}
style={[styles.recentItem, theme.shadows.subtle]}
>
<View style={styles.recentIconContainer}>
<LinearGradient
colors={theme.gradients.success}
style={styles.recentIcon}
>
<Ionicons name="bicycle" size={20} color="#fff" />
</LinearGradient>
</View>
<View style={styles.recentInfo}>
<Text style={styles.recentTitle}>Morning Cardio</Text>
<Text style={styles.recentSubtitle}>Yesterday, 7:30 AM</Text>
</View>
<Text style={styles.recentValue}>30m</Text>
</LinearGradient>
</View>
</View>
{/* Bottom Spacer for Tab Bar */}
<View style={{ height: 100 }} />
</ScrollView>
</View>
);
}
const styles = StyleSheet.create({
@@ -241,167 +120,110 @@ const styles = StyleSheet.create({
flex: 1,
backgroundColor: theme.colors.background,
},
content: {
paddingBottom: 20,
scrollContent: {
paddingTop: 60,
},
header: {
padding: 24,
paddingTop: 60,
paddingBottom: 32,
borderBottomLeftRadius: theme.borderRadius['2xl'],
borderBottomRightRadius: theme.borderRadius['2xl'],
marginBottom: 20,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 24,
marginBottom: 32,
},
greeting: {
fontSize: theme.typography.fontSize.base,
color: "rgba(255, 255, 255, 0.9)",
fontSize: 16,
color: theme.colors.gray600,
fontWeight: "500",
marginBottom: 4,
},
name: {
fontSize: theme.typography.fontSize['4xl'],
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.white,
},
statsContainer: {
flexDirection: "row",
justifyContent: "space-between",
paddingHorizontal: 16,
marginBottom: 24,
gap: 12,
},
statCard: {
flex: 1,
backgroundColor: theme.colors.white,
borderRadius: theme.borderRadius.xl,
padding: 16,
alignItems: "center",
borderWidth: 1,
borderColor: "rgba(255, 255, 255, 0.3)",
},
statIconContainer: {
marginBottom: 8,
},
statIconGradient: {
width: 48,
height: 48,
borderRadius: 24,
justifyContent: "center",
alignItems: "center",
},
statValue: {
fontSize: theme.typography.fontSize['2xl'],
fontWeight: theme.typography.fontWeight.bold,
fontSize: 32,
fontWeight: "800",
color: theme.colors.gray900,
marginTop: 4,
marginBottom: 4,
letterSpacing: -0.5,
},
statLabel: {
fontSize: theme.typography.fontSize.xs,
color: theme.colors.gray600,
textAlign: "center",
fontWeight: theme.typography.fontWeight.medium,
avatarContainer: {
shadowColor: "#000",
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.1,
shadowRadius: 12,
elevation: 5,
},
section: {
marginBottom: 24,
paddingHorizontal: 20,
},
sectionTitle: {
fontSize: theme.typography.fontSize.xl,
fontWeight: theme.typography.fontWeight.semibold,
color: theme.colors.gray900,
marginBottom: 12,
},
actionButton: {
flexDirection: "row",
alignItems: "center",
backgroundColor: theme.colors.white,
borderRadius: theme.borderRadius.xl,
padding: 16,
marginBottom: 12,
},
actionIcon: {
avatar: {
width: 56,
height: 56,
borderRadius: 28,
borderRadius: 20,
borderWidth: 2,
borderColor: "#fff",
},
placeholderAvatar: {
width: 56,
height: 56,
borderRadius: 20,
backgroundColor: theme.colors.primary,
justifyContent: "center",
alignItems: "center",
marginRight: 12,
borderWidth: 2,
borderColor: "#fff",
},
actionContent: {
flex: 1,
section: {
paddingHorizontal: 20,
marginBottom: 24,
},
actionTitle: {
fontSize: theme.typography.fontSize.base,
fontWeight: theme.typography.fontWeight.semibold,
color: theme.colors.gray900,
marginBottom: 2,
},
actionSubtitle: {
fontSize: theme.typography.fontSize.sm,
color: theme.colors.gray600,
},
membershipCard: {
borderRadius: theme.borderRadius.xl,
padding: 20,
},
membershipHeader: {
sectionHeader: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 12,
},
membershipType: {
fontSize: theme.typography.fontSize.xl,
fontWeight: theme.typography.fontWeight.semibold,
color: theme.colors.white,
},
statusBadge: {
backgroundColor: "rgba(255, 255, 255, 0.25)",
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: theme.borderRadius.lg,
borderWidth: 1,
borderColor: "rgba(255, 255, 255, 0.3)",
},
statusText: {
fontSize: theme.typography.fontSize.xs,
fontWeight: theme.typography.fontWeight.semibold,
color: theme.colors.white,
},
membershipEmail: {
fontSize: theme.typography.fontSize.sm,
color: "rgba(255, 255, 255, 0.9)",
marginBottom: 4,
},
membershipDate: {
fontSize: theme.typography.fontSize.xs,
color: "rgba(255, 255, 255, 0.7)",
},
emptyState: {
backgroundColor: theme.colors.white,
borderRadius: theme.borderRadius.xl,
padding: 32,
alignItems: "center",
},
emptyIconContainer: {
marginBottom: 16,
},
emptyIconGradient: {
width: 96,
height: 96,
borderRadius: 48,
sectionTitle: {
fontSize: 18,
fontWeight: "700",
color: theme.colors.gray900,
},
seeAll: {
fontSize: 14,
color: theme.colors.primary,
fontWeight: "600",
},
activityCard: {
gap: 12,
},
recentItem: {
flexDirection: "row",
alignItems: "center",
padding: 16,
borderRadius: 20,
backgroundColor: "#fff",
borderWidth: 1,
borderColor: "rgba(255, 255, 255, 0.6)",
},
recentIconContainer: {
marginRight: 16,
},
recentIcon: {
width: 48,
height: 48,
borderRadius: 16,
justifyContent: "center",
alignItems: "center",
},
emptyStateText: {
fontSize: theme.typography.fontSize.base,
fontWeight: theme.typography.fontWeight.semibold,
color: theme.colors.gray700,
recentInfo: {
flex: 1,
},
recentTitle: {
fontSize: 16,
fontWeight: "600",
color: theme.colors.gray900,
marginBottom: 4,
},
emptyStateSubtext: {
fontSize: theme.typography.fontSize.sm,
recentSubtitle: {
fontSize: 12,
color: theme.colors.gray500,
textAlign: "center",
},
recentValue: {
fontSize: 14,
fontWeight: "600",
color: theme.colors.gray900,
},
});
+188 -292
View File
@@ -1,235 +1,135 @@
import React from "react";
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Alert,
ScrollView,
} from "react-native";
import { useUser, useAuth } from "@clerk/clerk-expo";
import { useRouter } from "expo-router";
import { View, Text, StyleSheet, TouchableOpacity, Image, Alert } from "react-native";
import { useUser, useClerk } from "@clerk/clerk-expo";
import { Ionicons } from "@expo/vector-icons";
import { LinearGradient } from "expo-linear-gradient";
import { theme } from "../../styles/theme";
import { AnimatedButton } from "../../components/AnimatedButton";
import { GradientBackground } from "../../components/GradientBackground";
export default function ProfileScreen() {
const { user } = useUser();
const { signOut } = useAuth();
const router = useRouter();
const { signOut } = useClerk();
const handleLogout = async () => {
Alert.alert("Sign Out", "Are you sure you want to sign out?", [
{ text: "Cancel", style: "cancel" },
{
text: "Sign Out",
style: "destructive",
onPress: async () => {
try {
await signOut();
router.replace("/(auth)/sign-in");
} catch (error) {
Alert.alert("Error", "Failed to sign out");
}
},
},
]);
const handleSignOut = async () => {
try {
await signOut();
} catch (err) {
console.error("Error signing out:", err);
}
};
if (!user) {
return (
<View style={styles.container}>
<Text>Loading...</Text>
</View>
const confirmSignOut = () => {
Alert.alert(
"Sign Out",
"Are you sure you want to sign out?",
[
{ text: "Cancel", style: "cancel" },
{ text: "Sign Out", style: "destructive", onPress: handleSignOut },
]
);
}
};
return (
<ScrollView style={styles.container}>
<View style={styles.content}>
{/* Profile Header with Gradient */}
<LinearGradient
colors={theme.gradients.primary}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={[styles.profileCard, theme.shadows.strong]}
>
<View style={styles.container}>
<GradientBackground variant="primary" style={styles.header}>
<View style={styles.profileCard}>
<View style={styles.avatarContainer}>
{user.imageUrl ? (
<LinearGradient
colors={['rgba(255, 255, 255, 0.3)', 'rgba(255, 255, 255, 0.1)']}
style={styles.avatar}
>
<Text style={styles.avatarText}>
{user.firstName?.charAt(0)}
{user.lastName?.charAt(0)}
</Text>
</LinearGradient>
{user?.imageUrl ? (
<Image source={{ uri: user.imageUrl }} style={styles.avatar} />
) : (
<LinearGradient
colors={['rgba(255, 255, 255, 0.3)', 'rgba(255, 255, 255, 0.1)']}
style={styles.avatar}
>
<View style={styles.placeholderAvatar}>
<Ionicons name="person" size={40} color="#fff" />
</View>
)}
<View style={styles.editBadge}>
<Ionicons name="pencil" size={12} color={theme.colors.primary} />
</View>
</View>
<Text style={styles.name}>{user?.fullName || "User"}</Text>
<Text style={styles.email}>{user?.primaryEmailAddress?.emailAddress}</Text>
<View style={styles.memberBadge}>
<Text style={styles.memberText}>Premium Member</Text>
</View>
</View>
</GradientBackground>
<View style={styles.content}>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Account</Text>
<View style={[styles.infoCard, theme.shadows.subtle]}>
<TouchableOpacity style={styles.infoRow}>
<LinearGradient
colors={['rgba(59, 130, 246, 0.1)', 'rgba(59, 130, 246, 0.05)']}
style={styles.iconContainer}
>
<Ionicons name="person-outline" size={20} color={theme.colors.primary} />
</LinearGradient>
)}
</View>
<Text style={styles.name}>
{user.firstName} {user.lastName}
</Text>
<Text style={styles.email}>
{user.primaryEmailAddress?.emailAddress}
</Text>
{user.primaryPhoneNumber && (
<Text style={styles.phone}>
{user.primaryPhoneNumber.phoneNumber}
</Text>
)}
</LinearGradient>
{/* Account Information */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Account Information</Text>
<View style={[styles.infoCard, theme.shadows.medium]}>
<View style={styles.infoRow}>
<View style={styles.infoLabel}>
<LinearGradient
colors={['rgba(59, 130, 246, 0.15)', 'rgba(59, 130, 246, 0.05)']}
style={styles.infoIconContainer}
>
<Ionicons name="mail-outline" size={18} color={theme.colors.primary} />
</LinearGradient>
<Text style={styles.infoLabelText}>Email</Text>
</View>
<Text style={styles.infoValue}>
{user.primaryEmailAddress?.emailAddress}
</Text>
</View>
{user.primaryPhoneNumber && (
<View style={styles.infoRow}>
<View style={styles.infoLabel}>
<LinearGradient
colors={['rgba(16, 185, 129, 0.15)', 'rgba(16, 185, 129, 0.05)']}
style={styles.infoIconContainer}
>
<Ionicons name="call-outline" size={18} color={theme.colors.success} />
</LinearGradient>
<Text style={styles.infoLabelText}>Phone</Text>
</View>
<Text style={styles.infoValue}>
{user.primaryPhoneNumber.phoneNumber}
</Text>
</View>
)}
<View style={styles.infoRow}>
<View style={styles.infoLabel}>
<LinearGradient
colors={['rgba(245, 158, 11, 0.15)', 'rgba(245, 158, 11, 0.05)']}
style={styles.infoIconContainer}
>
<Ionicons name="calendar-outline" size={18} color={theme.colors.warning} />
</LinearGradient>
<Text style={styles.infoLabelText}>Member Since</Text>
</View>
<Text style={styles.infoValue}>
{new Date(user.createdAt!).toLocaleDateString()}
</Text>
</View>
<View style={[styles.infoRow, { borderBottomWidth: 0 }]}>
<View style={styles.infoLabel}>
<LinearGradient
colors={['rgba(16, 185, 129, 0.15)', 'rgba(16, 185, 129, 0.05)']}
style={styles.infoIconContainer}
>
<Ionicons
name="shield-checkmark-outline"
size={18}
color={theme.colors.success}
/>
</LinearGradient>
<Text style={styles.infoLabelText}>Email Verified</Text>
</View>
<Text style={styles.infoValue}>
{user.primaryEmailAddress?.verification?.status === "verified"
? "Yes"
: "No"}
</Text>
</View>
<Text style={styles.infoLabel}>Personal Details</Text>
<Ionicons name="chevron-forward" size={20} color={theme.colors.gray400} />
</TouchableOpacity>
<View style={styles.divider} />
<TouchableOpacity style={styles.infoRow}>
<LinearGradient
colors={['rgba(16, 185, 129, 0.1)', 'rgba(16, 185, 129, 0.05)']}
style={styles.iconContainer}
>
<Ionicons name="fitness-outline" size={20} color={theme.colors.success} />
</LinearGradient>
<Text style={styles.infoLabel}>Fitness Profile</Text>
<Ionicons name="chevron-forward" size={20} color={theme.colors.gray400} />
</TouchableOpacity>
<View style={styles.divider} />
<TouchableOpacity style={styles.infoRow}>
<LinearGradient
colors={['rgba(245, 158, 11, 0.1)', 'rgba(245, 158, 11, 0.05)']}
style={styles.iconContainer}
>
<Ionicons name="notifications-outline" size={20} color={theme.colors.warning} />
</LinearGradient>
<Text style={styles.infoLabel}>Notifications</Text>
<Ionicons name="chevron-forward" size={20} color={theme.colors.gray400} />
</TouchableOpacity>
</View>
</View>
{/* Quick Actions */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Quick Actions</Text>
<TouchableOpacity style={[styles.actionButton, theme.shadows.medium]} activeOpacity={0.7}>
<LinearGradient
colors={['rgba(59, 130, 246, 0.15)', 'rgba(59, 130, 246, 0.05)']}
style={styles.actionIconContainer}
>
<Ionicons name="person-outline" size={22} color={theme.colors.primary} />
</LinearGradient>
<Text style={styles.actionButtonText}>Edit Profile</Text>
<Ionicons name="chevron-forward" size={20} color={theme.colors.gray400} />
</TouchableOpacity>
<TouchableOpacity style={[styles.actionButton, theme.shadows.medium]} activeOpacity={0.7}>
<LinearGradient
colors={['rgba(245, 158, 11, 0.15)', 'rgba(245, 158, 11, 0.05)']}
style={styles.actionIconContainer}
>
<Ionicons name="notifications-outline" size={22} color={theme.colors.warning} />
</LinearGradient>
<Text style={styles.actionButtonText}>Notifications</Text>
<Ionicons name="chevron-forward" size={20} color={theme.colors.gray400} />
</TouchableOpacity>
<TouchableOpacity style={[styles.actionButton, theme.shadows.medium]} activeOpacity={0.7}>
<LinearGradient
colors={['rgba(139, 92, 246, 0.15)', 'rgba(139, 92, 246, 0.05)']}
style={styles.actionIconContainer}
>
<Ionicons name="card-outline" size={22} color={theme.colors.purple} />
</LinearGradient>
<Text style={styles.actionButtonText}>Payment History</Text>
<Ionicons name="chevron-forward" size={20} color={theme.colors.gray400} />
</TouchableOpacity>
<TouchableOpacity style={[styles.actionButton, theme.shadows.medium]} activeOpacity={0.7}>
<LinearGradient
colors={['rgba(107, 114, 128, 0.15)', 'rgba(107, 114, 128, 0.05)']}
style={styles.actionIconContainer}
>
<Ionicons name="settings-outline" size={22} color={theme.colors.gray600} />
</LinearGradient>
<Text style={styles.actionButtonText}>Settings</Text>
<Ionicons name="chevron-forward" size={20} color={theme.colors.gray400} />
</TouchableOpacity>
<Text style={styles.sectionTitle}>Support</Text>
<View style={[styles.infoCard, theme.shadows.subtle]}>
<TouchableOpacity style={styles.infoRow}>
<LinearGradient
colors={['rgba(139, 92, 246, 0.1)', 'rgba(139, 92, 246, 0.05)']}
style={styles.iconContainer}
>
<Ionicons name="help-circle-outline" size={20} color={theme.colors.secondary} />
</LinearGradient>
<Text style={styles.infoLabel}>Help Center</Text>
<Ionicons name="chevron-forward" size={20} color={theme.colors.gray400} />
</TouchableOpacity>
<View style={styles.divider} />
<TouchableOpacity style={styles.infoRow}>
<LinearGradient
colors={['rgba(107, 114, 128, 0.1)', 'rgba(107, 114, 128, 0.05)']}
style={styles.iconContainer}
>
<Ionicons name="shield-checkmark-outline" size={20} color={theme.colors.gray600} />
</LinearGradient>
<Text style={styles.infoLabel}>Privacy & Security</Text>
<Ionicons name="chevron-forward" size={20} color={theme.colors.gray400} />
</TouchableOpacity>
</View>
</View>
{/* Sign Out Button */}
<TouchableOpacity onPress={handleLogout} activeOpacity={0.8}>
<LinearGradient
colors={theme.gradients.danger}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={[styles.logoutButton, theme.shadows.glowDanger]}
>
<Ionicons name="log-out-outline" size={20} color="#fff" />
<Text style={styles.logoutText}>Sign Out</Text>
</LinearGradient>
</TouchableOpacity>
<AnimatedButton
title="Sign Out"
onPress={confirmSignOut}
variant="danger"
style={styles.signOutButton}
icon={<Ionicons name="log-out-outline" size={20} color="#fff" />}
/>
{/* App Version */}
<Text style={styles.version}>Version 1.0.0</Text>
</View>
</ScrollView>
</View>
);
}
@@ -238,134 +138,130 @@ const styles = StyleSheet.create({
flex: 1,
backgroundColor: theme.colors.background,
},
content: {
padding: 20,
header: {
paddingTop: 60,
paddingBottom: 30,
borderBottomLeftRadius: theme.borderRadius.xl,
borderBottomRightRadius: theme.borderRadius.xl,
alignItems: 'center',
},
profileCard: {
borderRadius: theme.borderRadius.xl,
padding: 28,
alignItems: "center",
marginBottom: 24,
alignItems: 'center',
},
avatarContainer: {
position: 'relative',
marginBottom: 16,
},
avatar: {
width: 96,
height: 96,
borderRadius: 48,
justifyContent: "center",
alignItems: "center",
borderWidth: 3,
borderColor: "rgba(255, 255, 255, 0.3)",
width: 100,
height: 100,
borderRadius: 50,
borderWidth: 4,
borderColor: 'rgba(255, 255, 255, 0.3)',
},
avatarText: {
fontSize: theme.typography.fontSize['4xl'],
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.white,
placeholderAvatar: {
width: 100,
height: 100,
borderRadius: 50,
backgroundColor: 'rgba(255, 255, 255, 0.2)',
justifyContent: 'center',
alignItems: 'center',
borderWidth: 4,
borderColor: 'rgba(255, 255, 255, 0.3)',
},
editBadge: {
position: 'absolute',
bottom: 0,
right: 0,
backgroundColor: theme.colors.white,
width: 32,
height: 32,
borderRadius: 16,
justifyContent: 'center',
alignItems: 'center',
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
name: {
fontSize: theme.typography.fontSize['2xl'],
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.white,
marginBottom: 6,
},
email: {
fontSize: theme.typography.fontSize.base,
color: "rgba(255, 255, 255, 0.9)",
marginBottom: 4,
},
phone: {
email: {
fontSize: theme.typography.fontSize.sm,
color: "rgba(255, 255, 255, 0.8)",
color: 'rgba(255, 255, 255, 0.8)',
marginBottom: 12,
},
memberBadge: {
backgroundColor: 'rgba(255, 255, 255, 0.2)',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: theme.borderRadius.full,
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.3)',
},
memberText: {
color: theme.colors.white,
fontSize: theme.typography.fontSize.xs,
fontWeight: theme.typography.fontWeight.bold,
letterSpacing: 0.5,
},
content: {
padding: 20,
marginTop: -20,
},
section: {
marginBottom: 24,
},
sectionTitle: {
fontSize: theme.typography.fontSize.xl,
fontWeight: theme.typography.fontWeight.semibold,
fontSize: theme.typography.fontSize.lg,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.gray900,
marginBottom: 12,
marginLeft: 4,
},
infoCard: {
backgroundColor: theme.colors.white,
borderRadius: theme.borderRadius.xl,
padding: 16,
padding: 8,
borderWidth: 1,
borderColor: theme.colors.gray100,
},
infoRow: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingVertical: 14,
borderBottomWidth: 1,
borderBottomColor: theme.colors.gray200,
flexDirection: 'row',
alignItems: 'center',
padding: 12,
},
infoLabel: {
flexDirection: "row",
alignItems: "center",
gap: 10,
},
infoIconContainer: {
iconContainer: {
width: 36,
height: 36,
borderRadius: 18,
justifyContent: "center",
alignItems: "center",
},
infoLabelText: {
fontSize: theme.typography.fontSize.sm,
color: theme.colors.gray700,
fontWeight: theme.typography.fontWeight.medium,
},
infoValue: {
fontSize: theme.typography.fontSize.sm,
color: theme.colors.gray900,
fontWeight: theme.typography.fontWeight.semibold,
},
actionButton: {
flexDirection: "row",
alignItems: "center",
backgroundColor: theme.colors.white,
borderRadius: theme.borderRadius.xl,
padding: 16,
marginBottom: 12,
},
actionIconContainer: {
width: 44,
height: 44,
borderRadius: 22,
justifyContent: "center",
alignItems: "center",
borderRadius: 10,
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
actionButtonText: {
infoLabel: {
flex: 1,
fontSize: theme.typography.fontSize.base,
color: theme.colors.gray900,
fontWeight: theme.typography.fontWeight.medium,
},
logoutButton: {
paddingVertical: 16,
borderRadius: theme.borderRadius.lg,
alignItems: "center",
justifyContent: "center",
flexDirection: "row",
gap: 8,
marginTop: 8,
marginBottom: 16,
divider: {
height: 1,
backgroundColor: theme.colors.gray100,
marginLeft: 60,
},
logoutText: {
color: theme.colors.white,
fontSize: theme.typography.fontSize.base,
fontWeight: theme.typography.fontWeight.semibold,
signOutButton: {
marginTop: 8,
},
version: {
textAlign: "center",
textAlign: 'center',
marginTop: 24,
color: theme.colors.gray400,
fontSize: theme.typography.fontSize.xs,
color: theme.colors.gray500,
marginTop: 8,
marginBottom: 20,
},
});