claude take :(
This commit is contained in:
1 parent
fc12cecd30
commit
73d2c4c1ed
13 files changed
+1460
-517
No files matched your search
@@ -1,15 +1,39 @@
|
||||
import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, ScrollView, Alert } from 'react-native'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { useAuth } from '@clerk/clerk-expo'
|
||||
import { LinearGradient } from 'expo-linear-gradient'
|
||||
import { Ionicons } from '@expo/vector-icons'
|
||||
import { attendanceApi, Attendance } from '../../api/attendance'
|
||||
|
||||
|
||||
import { theme } from '../../styles/theme'
|
||||
import { Animated } from 'react-native'
|
||||
|
||||
export default function AttendanceScreen() {
|
||||
const { getToken, userId } = useAuth()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [activeCheckIn, setActiveCheckIn] = useState<Attendance | null>(null)
|
||||
const [history, setHistory] = useState<Attendance[]>([])
|
||||
const pulseAnim = useRef(new Animated.Value(1)).current
|
||||
|
||||
useEffect(() => {
|
||||
if (activeCheckIn) {
|
||||
const pulse = Animated.loop(
|
||||
Animated.sequence([
|
||||
Animated.timing(pulseAnim, {
|
||||
toValue: 1.05,
|
||||
duration: 1000,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(pulseAnim, {
|
||||
toValue: 1,
|
||||
duration: 1000,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
])
|
||||
)
|
||||
pulse.start()
|
||||
return () => pulse.stop()
|
||||
}
|
||||
}, [activeCheckIn])
|
||||
|
||||
const fetchAttendance = async () => {
|
||||
try {
|
||||
@@ -70,42 +94,100 @@ export default function AttendanceScreen() {
|
||||
if (loading && !history.length) {
|
||||
return (
|
||||
<View style={styles.centered}>
|
||||
<ActivityIndicator size="large" color="#000" />
|
||||
<ActivityIndicator size="large" color={theme.colors.primary} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
|
||||
<Text style={styles.title}>Attendance</Text>
|
||||
<Text style={styles.subtitle}>Track your gym visits</Text>
|
||||
<LinearGradient
|
||||
colors={theme.gradients.primary}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={styles.header}
|
||||
>
|
||||
<Text style={styles.title}>Attendance</Text>
|
||||
<Text style={styles.subtitle}>Track your gym visits</Text>
|
||||
</LinearGradient>
|
||||
|
||||
<View style={styles.actionContainer}>
|
||||
{activeCheckIn ? (
|
||||
<View style={styles.activeCard}>
|
||||
<Text style={styles.activeText}>Currently Checked In</Text>
|
||||
<Text style={styles.timeText}>
|
||||
Since {new Date(activeCheckIn.checkInTime).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||
</Text>
|
||||
<TouchableOpacity style={styles.checkOutButton} onPress={handleCheckOut}>
|
||||
<Text style={styles.buttonText}>Check Out</Text>
|
||||
<LinearGradient
|
||||
colors={['rgba(16, 185, 129, 0.15)', 'rgba(5, 150, 105, 0.1)']}
|
||||
style={[styles.activeCard, theme.shadows.medium]}
|
||||
>
|
||||
<View style={styles.activeCardContent}>
|
||||
<View style={styles.activeIconContainer}>
|
||||
<LinearGradient
|
||||
colors={theme.gradients.success}
|
||||
style={styles.activeIcon}
|
||||
>
|
||||
<Ionicons name="checkmark-circle" size={32} color="#fff" />
|
||||
</LinearGradient>
|
||||
</View>
|
||||
<View style={styles.activeTextContainer}>
|
||||
<Text style={styles.activeText}>Currently Checked In</Text>
|
||||
<Text style={styles.timeText}>
|
||||
Since {new Date(activeCheckIn.checkInTime).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<TouchableOpacity onPress={handleCheckOut} activeOpacity={0.8}>
|
||||
<LinearGradient
|
||||
colors={theme.gradients.danger}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 0 }}
|
||||
style={[styles.checkOutButton, theme.shadows.medium]}
|
||||
>
|
||||
<Ionicons name="log-out-outline" size={20} color="#fff" style={{ marginRight: 8 }} />
|
||||
<Text style={styles.buttonText}>Check Out</Text>
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
) : (
|
||||
<TouchableOpacity style={styles.checkInButton} onPress={handleCheckIn}>
|
||||
<Text style={styles.buttonText}>Check In</Text>
|
||||
</TouchableOpacity>
|
||||
<Animated.View style={{ transform: [{ scale: pulseAnim }] }}>
|
||||
<TouchableOpacity onPress={handleCheckIn} activeOpacity={0.8}>
|
||||
<LinearGradient
|
||||
colors={theme.gradients.primary}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 0 }}
|
||||
style={[styles.checkInButton, theme.shadows.glow]}
|
||||
>
|
||||
<Ionicons name="log-in-outline" size={24} color="#fff" style={{ marginRight: 8 }} />
|
||||
<Text style={styles.checkInButtonText}>Check In</Text>
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<Text style={styles.sectionTitle}>Recent History</Text>
|
||||
{history.map((item) => (
|
||||
<View key={item.id} style={styles.historyItem}>
|
||||
<View>
|
||||
<Text style={styles.dateText}>
|
||||
{new Date(item.checkInTime).toLocaleDateString()}
|
||||
</Text>
|
||||
<Text style={styles.typeText}>{item.type.toUpperCase()}</Text>
|
||||
<LinearGradient
|
||||
key={item.id}
|
||||
colors={['rgba(255, 255, 255, 1)', 'rgba(249, 250, 251, 1)'] as const}
|
||||
style={[styles.historyItem, theme.shadows.medium]}
|
||||
>
|
||||
<View style={styles.historyLeft}>
|
||||
<View style={styles.historyIconContainer}>
|
||||
<LinearGradient
|
||||
colors={item.checkOutTime ? theme.gradients.success : theme.gradients.primary}
|
||||
style={styles.historyIcon}
|
||||
>
|
||||
<Ionicons
|
||||
name={item.checkOutTime ? "checkmark" : "time-outline"}
|
||||
size={16}
|
||||
color="#fff"
|
||||
/>
|
||||
</LinearGradient>
|
||||
</View>
|
||||
<View>
|
||||
<Text style={styles.dateText}>
|
||||
{new Date(item.checkInTime).toLocaleDateString()}
|
||||
</Text>
|
||||
<Text style={styles.typeText}>{item.type.toUpperCase()}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.timeContainer}>
|
||||
<Text style={styles.historyTime}>
|
||||
@@ -117,7 +199,7 @@ export default function AttendanceScreen() {
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
))}
|
||||
</ScrollView>
|
||||
)
|
||||
@@ -126,105 +208,147 @@ export default function AttendanceScreen() {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#f5f5f5',
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
content: {
|
||||
padding: 20,
|
||||
paddingBottom: 20,
|
||||
},
|
||||
centered: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
header: {
|
||||
paddingTop: 60,
|
||||
paddingBottom: 24,
|
||||
paddingHorizontal: 24,
|
||||
marginBottom: 24,
|
||||
borderBottomLeftRadius: theme.borderRadius.xl,
|
||||
borderBottomRightRadius: theme.borderRadius.xl,
|
||||
},
|
||||
title: {
|
||||
fontSize: 28,
|
||||
fontWeight: 'bold',
|
||||
fontSize: theme.typography.fontSize['3xl'],
|
||||
fontWeight: theme.typography.fontWeight.bold,
|
||||
color: theme.colors.white,
|
||||
marginBottom: 4,
|
||||
color: '#1a1a1a',
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 16,
|
||||
color: '#666',
|
||||
marginBottom: 24,
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
color: 'rgba(255, 255, 255, 0.9)',
|
||||
},
|
||||
actionContainer: {
|
||||
marginBottom: 32,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
checkInButton: {
|
||||
backgroundColor: '#000',
|
||||
paddingVertical: 16,
|
||||
borderRadius: 12,
|
||||
paddingVertical: 20,
|
||||
paddingHorizontal: 24,
|
||||
borderRadius: theme.borderRadius.xl,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 3,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
checkInButtonText: {
|
||||
color: theme.colors.white,
|
||||
fontSize: theme.typography.fontSize.xl,
|
||||
fontWeight: theme.typography.fontWeight.semibold,
|
||||
},
|
||||
checkOutButton: {
|
||||
backgroundColor: '#ff3b30',
|
||||
paddingVertical: 16,
|
||||
borderRadius: 12,
|
||||
paddingVertical: 14,
|
||||
paddingHorizontal: 20,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginTop: 16,
|
||||
},
|
||||
buttonText: {
|
||||
color: '#fff',
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
color: theme.colors.white,
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
fontWeight: theme.typography.fontWeight.semibold,
|
||||
},
|
||||
activeCard: {
|
||||
backgroundColor: '#fff',
|
||||
padding: 20,
|
||||
borderRadius: 16,
|
||||
borderRadius: theme.borderRadius.xl,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e0e0e0',
|
||||
borderColor: 'rgba(16, 185, 129, 0.2)',
|
||||
},
|
||||
activeCardContent: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
activeIconContainer: {
|
||||
marginRight: 16,
|
||||
},
|
||||
activeIcon: {
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: 28,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
activeTextContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
activeText: {
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
color: '#2e7d32',
|
||||
fontSize: theme.typography.fontSize.lg,
|
||||
fontWeight: theme.typography.fontWeight.semibold,
|
||||
color: theme.colors.gray900,
|
||||
marginBottom: 4,
|
||||
},
|
||||
timeText: {
|
||||
fontSize: 14,
|
||||
color: '#666',
|
||||
fontSize: theme.typography.fontSize.sm,
|
||||
color: theme.colors.gray600,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: '600',
|
||||
fontSize: theme.typography.fontSize.xl,
|
||||
fontWeight: theme.typography.fontWeight.semibold,
|
||||
marginBottom: 16,
|
||||
color: '#1a1a1a',
|
||||
paddingHorizontal: 20,
|
||||
color: theme.colors.gray900,
|
||||
},
|
||||
historyItem: {
|
||||
backgroundColor: '#fff',
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
borderRadius: theme.borderRadius.xl,
|
||||
marginBottom: 12,
|
||||
marginHorizontal: 20,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 2,
|
||||
elevation: 2,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(59, 130, 246, 0.1)',
|
||||
},
|
||||
historyLeft: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
},
|
||||
historyIconContainer: {
|
||||
marginRight: 4,
|
||||
},
|
||||
historyIcon: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 16,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
dateText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
color: '#1a1a1a',
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
fontWeight: theme.typography.fontWeight.semibold,
|
||||
color: theme.colors.gray900,
|
||||
},
|
||||
typeText: {
|
||||
fontSize: 12,
|
||||
color: '#666',
|
||||
fontSize: theme.typography.fontSize.xs,
|
||||
color: theme.colors.gray600,
|
||||
marginTop: 2,
|
||||
},
|
||||
timeContainer: {
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
historyTime: {
|
||||
fontSize: 14,
|
||||
color: '#444',
|
||||
fontSize: theme.typography.fontSize.sm,
|
||||
color: theme.colors.gray700,
|
||||
},
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
@@ -8,12 +8,15 @@ import {
|
||||
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";
|
||||
|
||||
export default function GoalsScreen() {
|
||||
const { userId, getToken } = useAuth();
|
||||
@@ -21,6 +24,7 @@ export default function GoalsScreen() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||
const fabScale = useRef(new Animated.Value(1)).current;
|
||||
|
||||
const fetchGoals = async () => {
|
||||
if (!userId) return;
|
||||
@@ -100,14 +104,19 @@ export default function GoalsScreen() {
|
||||
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
||||
}
|
||||
>
|
||||
<View style={styles.header}>
|
||||
<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.headerSubtitle}>
|
||||
Track your fitness journey progress
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
|
||||
{/* Stats Summary */}
|
||||
{goals.length > 0 && (
|
||||
@@ -179,12 +188,37 @@ export default function GoalsScreen() {
|
||||
</ScrollView>
|
||||
|
||||
{/* Floating Action Button */}
|
||||
<TouchableOpacity
|
||||
style={styles.fab}
|
||||
onPress={() => setShowCreateModal(true)}
|
||||
>
|
||||
<Ionicons name="add" size={28} color="#fff" />
|
||||
</TouchableOpacity>
|
||||
<Animated.View style={{ transform: [{ scale: fabScale }] }}>
|
||||
<TouchableOpacity
|
||||
onPress={() => setShowCreateModal(true)}
|
||||
onPressIn={() => {
|
||||
Animated.spring(fabScale, {
|
||||
toValue: 0.9,
|
||||
friction: 8,
|
||||
tension: 100,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
}}
|
||||
onPressOut={() => {
|
||||
Animated.spring(fabScale, {
|
||||
toValue: 1,
|
||||
friction: 8,
|
||||
tension: 100,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
}}
|
||||
activeOpacity={0.9}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={theme.gradients.primary}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={styles.fab}
|
||||
>
|
||||
<Ionicons name="add" size={28} color="#fff" />
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
|
||||
{/* Create Goal Modal */}
|
||||
<GoalCreationModal
|
||||
@@ -199,7 +233,7 @@ export default function GoalsScreen() {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: "#f3f4f6",
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
center: {
|
||||
flex: 1,
|
||||
@@ -207,18 +241,21 @@ const styles = StyleSheet.create({
|
||||
alignItems: "center",
|
||||
},
|
||||
header: {
|
||||
padding: 20,
|
||||
backgroundColor: "#fff",
|
||||
padding: 24,
|
||||
paddingTop: 60,
|
||||
paddingBottom: 24,
|
||||
marginBottom: 10,
|
||||
borderBottomLeftRadius: theme.borderRadius.xl,
|
||||
borderBottomRightRadius: theme.borderRadius.xl,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 28,
|
||||
fontWeight: "bold",
|
||||
color: "#111827",
|
||||
fontSize: theme.typography.fontSize['3xl'],
|
||||
fontWeight: theme.typography.fontWeight.bold,
|
||||
color: theme.colors.white,
|
||||
},
|
||||
headerSubtitle: {
|
||||
fontSize: 16,
|
||||
color: "#6b7280",
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
color: "rgba(255, 255, 255, 0.9)",
|
||||
marginTop: 4,
|
||||
},
|
||||
statsContainer: {
|
||||
@@ -228,20 +265,18 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
statCard: {
|
||||
flex: 1,
|
||||
backgroundColor: "#fff",
|
||||
backgroundColor: theme.colors.white,
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
borderRadius: theme.borderRadius.xl,
|
||||
alignItems: "center",
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 2,
|
||||
elevation: 2,
|
||||
...theme.shadows.medium,
|
||||
borderWidth: 1,
|
||||
borderColor: "rgba(59, 130, 246, 0.1)",
|
||||
},
|
||||
statValue: {
|
||||
fontSize: 24,
|
||||
fontWeight: "bold",
|
||||
color: "#2563eb",
|
||||
fontSize: theme.typography.fontSize['2xl'],
|
||||
fontWeight: theme.typography.fontWeight.bold,
|
||||
color: theme.colors.primary,
|
||||
marginBottom: 4,
|
||||
},
|
||||
statLabel: {
|
||||
@@ -281,16 +316,11 @@ const styles = StyleSheet.create({
|
||||
position: "absolute",
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: 28,
|
||||
backgroundColor: "#2563eb",
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: 32,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 8,
|
||||
elevation: 8,
|
||||
...theme.shadows.glow,
|
||||
},
|
||||
});
|
||||
@@ -1,18 +1,30 @@
|
||||
import React from "react";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
TouchableOpacity,
|
||||
Animated,
|
||||
} 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 { theme } from "../../styles/theme";
|
||||
|
||||
export default function HomeScreen() {
|
||||
const { user, isLoaded } = useUser();
|
||||
const router = useRouter();
|
||||
const fadeAnim = useRef(new Animated.Value(0)).current;
|
||||
|
||||
useEffect(() => {
|
||||
Animated.timing(fadeAnim, {
|
||||
toValue: 1,
|
||||
duration: 500,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
}, []);
|
||||
|
||||
if (!isLoaded || !user) {
|
||||
return (
|
||||
@@ -27,32 +39,67 @@ export default function HomeScreen() {
|
||||
|
||||
return (
|
||||
<ScrollView style={styles.container}>
|
||||
<View style={styles.content}>
|
||||
{/* Welcome Header */}
|
||||
<View style={styles.header}>
|
||||
<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>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
|
||||
{/* Quick Stats */}
|
||||
{/* Quick Stats with Glassmorphism */}
|
||||
<View style={styles.statsContainer}>
|
||||
<View style={styles.statCard}>
|
||||
<Ionicons name="calendar-outline" size={32} color="#2563eb" />
|
||||
<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>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
|
||||
<View style={styles.statCard}>
|
||||
<Ionicons name="flame-outline" size={32} color="#ef4444" />
|
||||
<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>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
|
||||
<View style={styles.statCard}>
|
||||
<Ionicons name="trophy-outline" size={32} color="#f59e0b" />
|
||||
<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>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
</View>
|
||||
|
||||
{/* Quick Actions */}
|
||||
@@ -60,12 +107,16 @@ export default function HomeScreen() {
|
||||
<Text style={styles.sectionTitle}>Quick Actions</Text>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.actionButton}
|
||||
style={[styles.actionButton, theme.shadows.medium]}
|
||||
onPress={() => router.push("/fitness-profile")}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.actionIcon}>
|
||||
<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" />
|
||||
</View>
|
||||
</LinearGradient>
|
||||
<View style={styles.actionContent}>
|
||||
<Text style={styles.actionTitle}>Fitness Profile</Text>
|
||||
<Text style={styles.actionSubtitle}>
|
||||
@@ -75,12 +126,16 @@ export default function HomeScreen() {
|
||||
<Ionicons name="chevron-forward" size={20} color="#9ca3af" />
|
||||
</TouchableOpacity>
|
||||
|
||||
|
||||
|
||||
<View style={styles.actionButton}>
|
||||
<View style={styles.actionIcon}>
|
||||
<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" />
|
||||
</View>
|
||||
</LinearGradient>
|
||||
<View style={styles.actionContent}>
|
||||
<Text style={styles.actionTitle}>Check In</Text>
|
||||
<Text style={styles.actionSubtitle}>
|
||||
@@ -88,13 +143,18 @@ export default function HomeScreen() {
|
||||
</Text>
|
||||
</View>
|
||||
<Ionicons name="chevron-forward" size={20} color="#9ca3af" />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
|
||||
<View style={styles.actionButton}>
|
||||
<View style={styles.actionIcon}>
|
||||
<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" />
|
||||
</View>
|
||||
</LinearGradient>
|
||||
<View style={styles.actionContent}>
|
||||
<Text style={styles.actionTitle}>View Schedule</Text>
|
||||
<Text style={styles.actionSubtitle}>
|
||||
@@ -102,24 +162,35 @@ export default function HomeScreen() {
|
||||
</Text>
|
||||
</View>
|
||||
<Ionicons name="chevron-forward" size={20} color="#9ca3af" />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.actionButton}>
|
||||
<View style={styles.actionIcon}>
|
||||
<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" />
|
||||
</View>
|
||||
</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" />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Membership Info */}
|
||||
{/* Membership Info with Gradient */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Membership</Text>
|
||||
<View style={styles.membershipCard}>
|
||||
<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}>
|
||||
@@ -132,21 +203,28 @@ export default function HomeScreen() {
|
||||
<Text style={styles.membershipDate}>
|
||||
Member since {new Date(user.createdAt!).toLocaleDateString()}
|
||||
</Text>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
</View>
|
||||
|
||||
{/* Recent Activity */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Recent Activity</Text>
|
||||
<View style={styles.emptyState}>
|
||||
<Ionicons name="barbell-outline" size={48} color="#d1d5db" />
|
||||
<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>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
@@ -161,81 +239,90 @@ function getGreeting(): string {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: "#f5f5f5",
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
content: {
|
||||
padding: 20,
|
||||
paddingBottom: 20,
|
||||
},
|
||||
header: {
|
||||
marginBottom: 24,
|
||||
padding: 24,
|
||||
paddingTop: 60,
|
||||
paddingBottom: 32,
|
||||
borderBottomLeftRadius: theme.borderRadius['2xl'],
|
||||
borderBottomRightRadius: theme.borderRadius['2xl'],
|
||||
marginBottom: 20,
|
||||
},
|
||||
greeting: {
|
||||
fontSize: 16,
|
||||
color: "#6b7280",
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
color: "rgba(255, 255, 255, 0.9)",
|
||||
marginBottom: 4,
|
||||
},
|
||||
name: {
|
||||
fontSize: 32,
|
||||
fontWeight: "bold",
|
||||
color: "#1a1a1a",
|
||||
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: "white",
|
||||
borderRadius: 12,
|
||||
backgroundColor: theme.colors.white,
|
||||
borderRadius: theme.borderRadius.xl,
|
||||
padding: 16,
|
||||
alignItems: "center",
|
||||
marginHorizontal: 4,
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 2,
|
||||
elevation: 2,
|
||||
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: 24,
|
||||
fontWeight: "bold",
|
||||
color: "#1a1a1a",
|
||||
marginTop: 8,
|
||||
fontSize: theme.typography.fontSize['2xl'],
|
||||
fontWeight: theme.typography.fontWeight.bold,
|
||||
color: theme.colors.gray900,
|
||||
marginTop: 4,
|
||||
marginBottom: 4,
|
||||
},
|
||||
statLabel: {
|
||||
fontSize: 12,
|
||||
color: "#6b7280",
|
||||
fontSize: theme.typography.fontSize.xs,
|
||||
color: theme.colors.gray600,
|
||||
textAlign: "center",
|
||||
fontWeight: theme.typography.fontWeight.medium,
|
||||
},
|
||||
section: {
|
||||
marginBottom: 24,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: "600",
|
||||
color: "#1a1a1a",
|
||||
fontSize: theme.typography.fontSize.xl,
|
||||
fontWeight: theme.typography.fontWeight.semibold,
|
||||
color: theme.colors.gray900,
|
||||
marginBottom: 12,
|
||||
},
|
||||
actionButton: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
backgroundColor: "white",
|
||||
borderRadius: 12,
|
||||
backgroundColor: theme.colors.white,
|
||||
borderRadius: theme.borderRadius.xl,
|
||||
padding: 16,
|
||||
marginBottom: 8,
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 2,
|
||||
elevation: 2,
|
||||
marginBottom: 12,
|
||||
},
|
||||
actionIcon: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 24,
|
||||
backgroundColor: "#f0f9ff",
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: 28,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginRight: 12,
|
||||
@@ -244,77 +331,77 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
},
|
||||
actionTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: "600",
|
||||
color: "#1a1a1a",
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
fontWeight: theme.typography.fontWeight.semibold,
|
||||
color: theme.colors.gray900,
|
||||
marginBottom: 2,
|
||||
},
|
||||
actionSubtitle: {
|
||||
fontSize: 14,
|
||||
color: "#6b7280",
|
||||
fontSize: theme.typography.fontSize.sm,
|
||||
color: theme.colors.gray600,
|
||||
},
|
||||
membershipCard: {
|
||||
backgroundColor: "white",
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 2,
|
||||
elevation: 2,
|
||||
borderRadius: theme.borderRadius.xl,
|
||||
padding: 20,
|
||||
},
|
||||
membershipHeader: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: 8,
|
||||
marginBottom: 12,
|
||||
},
|
||||
membershipType: {
|
||||
fontSize: 18,
|
||||
fontWeight: "600",
|
||||
color: "#1a1a1a",
|
||||
fontSize: theme.typography.fontSize.xl,
|
||||
fontWeight: theme.typography.fontWeight.semibold,
|
||||
color: theme.colors.white,
|
||||
},
|
||||
statusBadge: {
|
||||
backgroundColor: "#dcfce7",
|
||||
backgroundColor: "rgba(255, 255, 255, 0.25)",
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 12,
|
||||
paddingVertical: 6,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
borderWidth: 1,
|
||||
borderColor: "rgba(255, 255, 255, 0.3)",
|
||||
},
|
||||
statusText: {
|
||||
fontSize: 12,
|
||||
fontWeight: "600",
|
||||
color: "#16a34a",
|
||||
fontSize: theme.typography.fontSize.xs,
|
||||
fontWeight: theme.typography.fontWeight.semibold,
|
||||
color: theme.colors.white,
|
||||
},
|
||||
membershipEmail: {
|
||||
fontSize: 14,
|
||||
color: "#6b7280",
|
||||
fontSize: theme.typography.fontSize.sm,
|
||||
color: "rgba(255, 255, 255, 0.9)",
|
||||
marginBottom: 4,
|
||||
},
|
||||
membershipDate: {
|
||||
fontSize: 12,
|
||||
color: "#9ca3af",
|
||||
fontSize: theme.typography.fontSize.xs,
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
},
|
||||
emptyState: {
|
||||
backgroundColor: "white",
|
||||
borderRadius: 12,
|
||||
backgroundColor: theme.colors.white,
|
||||
borderRadius: theme.borderRadius.xl,
|
||||
padding: 32,
|
||||
alignItems: "center",
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 2,
|
||||
elevation: 2,
|
||||
},
|
||||
emptyIconContainer: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
emptyIconGradient: {
|
||||
width: 96,
|
||||
height: 96,
|
||||
borderRadius: 48,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
emptyStateText: {
|
||||
fontSize: 16,
|
||||
fontWeight: "600",
|
||||
color: "#6b7280",
|
||||
marginTop: 12,
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
fontWeight: theme.typography.fontWeight.semibold,
|
||||
color: theme.colors.gray700,
|
||||
marginBottom: 4,
|
||||
},
|
||||
emptyStateSubtext: {
|
||||
fontSize: 14,
|
||||
color: "#9ca3af",
|
||||
fontSize: theme.typography.fontSize.sm,
|
||||
color: theme.colors.gray500,
|
||||
textAlign: "center",
|
||||
},
|
||||
});
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
import { useUser, useAuth } from "@clerk/clerk-expo";
|
||||
import { useRouter } from "expo-router";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import { theme } from "../../styles/theme";
|
||||
|
||||
export default function ProfileScreen() {
|
||||
const { user } = useUser();
|
||||
@@ -45,20 +47,31 @@ export default function ProfileScreen() {
|
||||
return (
|
||||
<ScrollView style={styles.container}>
|
||||
<View style={styles.content}>
|
||||
{/* Profile Header */}
|
||||
<View style={styles.profileCard}>
|
||||
{/* 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.avatarContainer}>
|
||||
{user.imageUrl ? (
|
||||
<View style={styles.avatar}>
|
||||
<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>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
) : (
|
||||
<View style={styles.avatar}>
|
||||
<LinearGradient
|
||||
colors={['rgba(255, 255, 255, 0.3)', 'rgba(255, 255, 255, 0.1)']}
|
||||
style={styles.avatar}
|
||||
>
|
||||
<Ionicons name="person" size={40} color="#fff" />
|
||||
</View>
|
||||
</LinearGradient>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -74,58 +87,80 @@ export default function ProfileScreen() {
|
||||
{user.primaryPhoneNumber.phoneNumber}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</LinearGradient>
|
||||
|
||||
{/* Account Information */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Account Information</Text>
|
||||
|
||||
<View style={styles.infoRow}>
|
||||
<View style={styles.infoLabel}>
|
||||
<Ionicons name="mail-outline" size={20} color="#666" />
|
||||
<Text style={styles.infoLabelText}>Email</Text>
|
||||
</View>
|
||||
<Text style={styles.infoValue}>
|
||||
{user.primaryEmailAddress?.emailAddress}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{user.primaryPhoneNumber && (
|
||||
<View style={[styles.infoCard, theme.shadows.medium]}>
|
||||
<View style={styles.infoRow}>
|
||||
<View style={styles.infoLabel}>
|
||||
<Ionicons name="call-outline" size={20} color="#666" />
|
||||
<Text style={styles.infoLabelText}>Phone</Text>
|
||||
<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.primaryPhoneNumber.phoneNumber}
|
||||
{user.primaryEmailAddress?.emailAddress}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={styles.infoRow}>
|
||||
<View style={styles.infoLabel}>
|
||||
<Ionicons name="calendar-outline" size={20} color="#666" />
|
||||
<Text style={styles.infoLabelText}>Member Since</Text>
|
||||
</View>
|
||||
<Text style={styles.infoValue}>
|
||||
{new Date(user.createdAt!).toLocaleDateString()}
|
||||
</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}>
|
||||
<Ionicons
|
||||
name="shield-checkmark-outline"
|
||||
size={20}
|
||||
color="#666"
|
||||
/>
|
||||
<Text style={styles.infoLabelText}>Email Verified</Text>
|
||||
<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.infoValue}>
|
||||
{user.primaryEmailAddress?.verification?.status === "verified"
|
||||
? "Yes"
|
||||
: "No"}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -133,35 +168,62 @@ export default function ProfileScreen() {
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Quick Actions</Text>
|
||||
|
||||
<TouchableOpacity style={styles.actionButton}>
|
||||
<Ionicons name="person-outline" size={24} color="#2563eb" />
|
||||
<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="#999" />
|
||||
<Ionicons name="chevron-forward" size={20} color={theme.colors.gray400} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity style={styles.actionButton}>
|
||||
<Ionicons name="notifications-outline" size={24} color="#2563eb" />
|
||||
<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="#999" />
|
||||
<Ionicons name="chevron-forward" size={20} color={theme.colors.gray400} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity style={styles.actionButton}>
|
||||
<Ionicons name="card-outline" size={24} color="#2563eb" />
|
||||
<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="#999" />
|
||||
<Ionicons name="chevron-forward" size={20} color={theme.colors.gray400} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity style={styles.actionButton}>
|
||||
<Ionicons name="settings-outline" size={24} color="#2563eb" />
|
||||
<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="#999" />
|
||||
<Ionicons name="chevron-forward" size={20} color={theme.colors.gray400} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Sign Out Button */}
|
||||
<TouchableOpacity style={styles.logoutButton} onPress={handleLogout}>
|
||||
<Ionicons name="log-out-outline" size={20} color="#fff" />
|
||||
<Text style={styles.logoutText}>Sign Out</Text>
|
||||
<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>
|
||||
|
||||
{/* App Version */}
|
||||
@@ -174,133 +236,135 @@ export default function ProfileScreen() {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: "#f5f5f5",
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
content: {
|
||||
padding: 20,
|
||||
paddingTop: 60,
|
||||
},
|
||||
profileCard: {
|
||||
backgroundColor: "white",
|
||||
borderRadius: 16,
|
||||
padding: 24,
|
||||
borderRadius: theme.borderRadius.xl,
|
||||
padding: 28,
|
||||
alignItems: "center",
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 8,
|
||||
elevation: 4,
|
||||
marginBottom: 24,
|
||||
},
|
||||
avatarContainer: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
avatar: {
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: 40,
|
||||
backgroundColor: "#2563eb",
|
||||
width: 96,
|
||||
height: 96,
|
||||
borderRadius: 48,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
borderWidth: 3,
|
||||
borderColor: "rgba(255, 255, 255, 0.3)",
|
||||
},
|
||||
avatarText: {
|
||||
fontSize: 32,
|
||||
fontWeight: "bold",
|
||||
color: "#fff",
|
||||
fontSize: theme.typography.fontSize['4xl'],
|
||||
fontWeight: theme.typography.fontWeight.bold,
|
||||
color: theme.colors.white,
|
||||
},
|
||||
name: {
|
||||
fontSize: 24,
|
||||
fontWeight: "bold",
|
||||
color: "#1a1a1a",
|
||||
marginBottom: 4,
|
||||
fontSize: theme.typography.fontSize['2xl'],
|
||||
fontWeight: theme.typography.fontWeight.bold,
|
||||
color: theme.colors.white,
|
||||
marginBottom: 6,
|
||||
},
|
||||
email: {
|
||||
fontSize: 16,
|
||||
color: "#666",
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
color: "rgba(255, 255, 255, 0.9)",
|
||||
marginBottom: 4,
|
||||
},
|
||||
phone: {
|
||||
fontSize: 14,
|
||||
color: "#999",
|
||||
fontSize: theme.typography.fontSize.sm,
|
||||
color: "rgba(255, 255, 255, 0.8)",
|
||||
},
|
||||
section: {
|
||||
backgroundColor: "white",
|
||||
borderRadius: 16,
|
||||
padding: 20,
|
||||
marginBottom: 16,
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 4,
|
||||
elevation: 2,
|
||||
marginBottom: 24,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: "600",
|
||||
color: "#1a1a1a",
|
||||
marginBottom: 16,
|
||||
fontSize: theme.typography.fontSize.xl,
|
||||
fontWeight: theme.typography.fontWeight.semibold,
|
||||
color: theme.colors.gray900,
|
||||
marginBottom: 12,
|
||||
},
|
||||
infoCard: {
|
||||
backgroundColor: theme.colors.white,
|
||||
borderRadius: theme.borderRadius.xl,
|
||||
padding: 16,
|
||||
},
|
||||
infoRow: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
paddingVertical: 12,
|
||||
paddingVertical: 14,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: "#f0f0f0",
|
||||
borderBottomColor: theme.colors.gray200,
|
||||
},
|
||||
infoLabel: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
gap: 10,
|
||||
},
|
||||
infoIconContainer: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 18,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
infoLabelText: {
|
||||
fontSize: 14,
|
||||
color: "#666",
|
||||
fontWeight: "500",
|
||||
fontSize: theme.typography.fontSize.sm,
|
||||
color: theme.colors.gray700,
|
||||
fontWeight: theme.typography.fontWeight.medium,
|
||||
},
|
||||
infoValue: {
|
||||
fontSize: 14,
|
||||
color: "#1a1a1a",
|
||||
fontWeight: "500",
|
||||
fontSize: theme.typography.fontSize.sm,
|
||||
color: theme.colors.gray900,
|
||||
fontWeight: theme.typography.fontWeight.semibold,
|
||||
},
|
||||
actionButton: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingVertical: 16,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: "#f0f0f0",
|
||||
backgroundColor: theme.colors.white,
|
||||
borderRadius: theme.borderRadius.xl,
|
||||
padding: 16,
|
||||
marginBottom: 12,
|
||||
},
|
||||
actionIconContainer: {
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 22,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginRight: 12,
|
||||
},
|
||||
actionButtonText: {
|
||||
flex: 1,
|
||||
fontSize: 16,
|
||||
color: "#1a1a1a",
|
||||
marginLeft: 12,
|
||||
fontWeight: "500",
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
color: theme.colors.gray900,
|
||||
fontWeight: theme.typography.fontWeight.medium,
|
||||
},
|
||||
logoutButton: {
|
||||
backgroundColor: "#ef4444",
|
||||
paddingVertical: 16,
|
||||
borderRadius: 12,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
flexDirection: "row",
|
||||
gap: 8,
|
||||
marginTop: 8,
|
||||
marginBottom: 16,
|
||||
shadowColor: "#ef4444",
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 8,
|
||||
elevation: 4,
|
||||
},
|
||||
logoutText: {
|
||||
color: "white",
|
||||
fontSize: 16,
|
||||
fontWeight: "600",
|
||||
color: theme.colors.white,
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
fontWeight: theme.typography.fontWeight.semibold,
|
||||
},
|
||||
version: {
|
||||
textAlign: "center",
|
||||
fontSize: 12,
|
||||
color: "#999",
|
||||
fontSize: theme.typography.fontSize.xs,
|
||||
color: theme.colors.gray500,
|
||||
marginTop: 8,
|
||||
marginBottom: 20,
|
||||
},
|
||||
|
||||
@@ -2,7 +2,9 @@ import { useEffect, useState } from "react";
|
||||
import { View, Text, FlatList, ActivityIndicator, StyleSheet, RefreshControl } from "react-native";
|
||||
import { useAuth } from "@clerk/clerk-expo";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import { API_BASE_URL, API_ENDPOINTS } from "../../config/api";
|
||||
import { theme } from "../../styles/theme";
|
||||
|
||||
interface Recommendation {
|
||||
id: string;
|
||||
@@ -70,37 +72,71 @@ export default function RecommendationsScreen() {
|
||||
if (loading) {
|
||||
return (
|
||||
<View style={styles.centered}>
|
||||
<ActivityIndicator size="large" color="#000" />
|
||||
<ActivityIndicator size="large" color={theme.colors.primary} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.header}>AI Recommendations</Text>
|
||||
<LinearGradient
|
||||
colors={theme.gradients.primary}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={styles.header}
|
||||
>
|
||||
<Text style={styles.headerTitle}>AI Recommendations</Text>
|
||||
</LinearGradient>
|
||||
|
||||
{/* AI Context Info Banner */}
|
||||
<View style={styles.infoBanner}>
|
||||
<Ionicons name="sparkles" size={20} color="#2563eb" />
|
||||
{/* AI Context Info Banner with Glassmorphism */}
|
||||
<LinearGradient
|
||||
colors={['rgba(59, 130, 246, 0.15)', 'rgba(139, 92, 246, 0.1)'] as const}
|
||||
style={styles.infoBanner}
|
||||
>
|
||||
<View style={styles.infoBannerIconContainer}>
|
||||
<LinearGradient
|
||||
colors={theme.gradients.primary}
|
||||
style={styles.infoBannerIcon}
|
||||
>
|
||||
<Ionicons name="sparkles" size={16} color="#fff" />
|
||||
</LinearGradient>
|
||||
</View>
|
||||
<Text style={styles.infoBannerText}>
|
||||
Personalized based on your active fitness goals and progress
|
||||
</Text>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
|
||||
<FlatList
|
||||
data={recommendations}
|
||||
keyExtractor={(item) => item.id}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
|
||||
contentContainerStyle={styles.listContent}
|
||||
ListEmptyComponent={
|
||||
<View style={styles.emptyContainer}>
|
||||
<View style={styles.emptyIconContainer}>
|
||||
<LinearGradient
|
||||
colors={['rgba(209, 213, 219, 0.3)', 'rgba(209, 213, 219, 0.1)'] as const}
|
||||
style={styles.emptyIconGradient}
|
||||
>
|
||||
<Ionicons name="sparkles-outline" size={48} color="#9ca3af" />
|
||||
</LinearGradient>
|
||||
</View>
|
||||
<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}>
|
||||
<LinearGradient
|
||||
colors={['rgba(255, 255, 255, 1)', 'rgba(249, 250, 251, 1)'] as const}
|
||||
style={[styles.card, theme.shadows.medium]}
|
||||
>
|
||||
<View style={styles.cardHeader}>
|
||||
<Text style={styles.status}>{item.status.toUpperCase()}</Text>
|
||||
<LinearGradient
|
||||
colors={theme.gradients.success}
|
||||
style={styles.statusBadge}
|
||||
>
|
||||
<Text style={styles.statusText}>{item.status.toUpperCase()}</Text>
|
||||
</LinearGradient>
|
||||
<Text style={styles.date}>{new Date(item.createdAt).toLocaleDateString()}</Text>
|
||||
</View>
|
||||
|
||||
@@ -120,7 +156,7 @@ export default function RecommendationsScreen() {
|
||||
<Text style={styles.content}>{item.dietPlan}</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</LinearGradient>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
@@ -130,83 +166,99 @@ export default function RecommendationsScreen() {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#f5f5f5',
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
header: {
|
||||
fontSize: 28,
|
||||
fontWeight: 'bold',
|
||||
padding: 20,
|
||||
paddingBottom: 12,
|
||||
color: '#1a1a1a',
|
||||
paddingTop: 60,
|
||||
paddingBottom: 24,
|
||||
paddingHorizontal: 24,
|
||||
borderBottomLeftRadius: theme.borderRadius.xl,
|
||||
borderBottomRightRadius: theme.borderRadius.xl,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: theme.typography.fontSize['3xl'],
|
||||
fontWeight: theme.typography.fontWeight.bold,
|
||||
color: theme.colors.white,
|
||||
},
|
||||
infoBanner: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#eff6ff',
|
||||
marginHorizontal: 16,
|
||||
marginTop: 16,
|
||||
marginBottom: 12,
|
||||
padding: 12,
|
||||
borderRadius: 8,
|
||||
borderLeftWidth: 3,
|
||||
borderLeftColor: '#2563eb',
|
||||
gap: 8,
|
||||
padding: 14,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(59, 130, 246, 0.2)',
|
||||
gap: 10,
|
||||
},
|
||||
infoBannerIconContainer: {
|
||||
marginRight: 4,
|
||||
},
|
||||
infoBannerIcon: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 16,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
infoBannerText: {
|
||||
flex: 1,
|
||||
fontSize: 13,
|
||||
color: '#1e40af',
|
||||
fontSize: theme.typography.fontSize.sm,
|
||||
color: theme.colors.gray700,
|
||||
lineHeight: 18,
|
||||
fontWeight: theme.typography.fontWeight.medium,
|
||||
},
|
||||
centered: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#f5f5f5',
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
listContent: {
|
||||
padding: 16,
|
||||
},
|
||||
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,
|
||||
padding: 18,
|
||||
marginBottom: 14,
|
||||
borderRadius: theme.borderRadius.xl,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(59, 130, 246, 0.1)',
|
||||
},
|
||||
cardHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 12,
|
||||
marginBottom: 14,
|
||||
paddingBottom: 12,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#e0e0e0',
|
||||
borderBottomColor: theme.colors.gray200,
|
||||
},
|
||||
status: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
color: '#2e7d32',
|
||||
backgroundColor: '#e8f5e9',
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 4,
|
||||
statusBadge: {
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 5,
|
||||
borderRadius: theme.borderRadius.md,
|
||||
},
|
||||
statusText: {
|
||||
fontSize: theme.typography.fontSize.xs,
|
||||
fontWeight: theme.typography.fontWeight.semibold,
|
||||
color: theme.colors.white,
|
||||
},
|
||||
date: {
|
||||
fontSize: 12,
|
||||
color: '#666',
|
||||
fontSize: theme.typography.fontSize.xs,
|
||||
color: theme.colors.gray600,
|
||||
fontWeight: theme.typography.fontWeight.medium,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#1a1a1a',
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
fontWeight: theme.typography.fontWeight.semibold,
|
||||
color: theme.colors.gray900,
|
||||
marginTop: 12,
|
||||
marginBottom: 6,
|
||||
},
|
||||
content: {
|
||||
fontSize: 14,
|
||||
color: '#333',
|
||||
fontSize: theme.typography.fontSize.sm,
|
||||
color: theme.colors.gray700,
|
||||
lineHeight: 20,
|
||||
},
|
||||
emptyContainer: {
|
||||
@@ -214,16 +266,26 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 60,
|
||||
},
|
||||
emptyIconContainer: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
emptyIconGradient: {
|
||||
width: 96,
|
||||
height: 96,
|
||||
borderRadius: 48,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
empty: {
|
||||
textAlign: 'center',
|
||||
fontSize: 16,
|
||||
color: '#666',
|
||||
fontWeight: '500',
|
||||
fontSize: theme.typography.fontSize.base,
|
||||
color: theme.colors.gray700,
|
||||
fontWeight: theme.typography.fontWeight.semibold,
|
||||
marginBottom: 4,
|
||||
},
|
||||
emptySub: {
|
||||
textAlign: 'center',
|
||||
fontSize: 14,
|
||||
color: '#999',
|
||||
marginTop: 8,
|
||||
fontSize: theme.typography.fontSize.sm,
|
||||
color: theme.colors.gray500,
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user