redesign completed
alot of work ahead
This commit is contained in:
1 parent
73d2c4c1ed
commit
febcdc111e
18 files changed
+1591
-845
No files matched your search
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user