basic stracture and auth
in memory db :)
This commit is contained in:
commit
3a554ba434
71 files changed
+28535
No files matched your search
@@ -0,0 +1,36 @@
|
||||
import { Tabs } from 'expo-router'
|
||||
import { Ionicons } from '@expo/vector-icons'
|
||||
|
||||
export default function TabLayout() {
|
||||
return (
|
||||
<Tabs>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: 'Home',
|
||||
tabBarIcon: ({ color, size }) => (
|
||||
<Ionicons name="home" size={size} color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="profile"
|
||||
options={{
|
||||
title: 'Profile',
|
||||
tabBarIcon: ({ color, size }) => (
|
||||
<Ionicons name="person" size={size} color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="attendance"
|
||||
options={{
|
||||
title: 'Attendance',
|
||||
tabBarIcon: ({ color, size }) => (
|
||||
<Ionicons name="calendar" size={size} color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { View, Text, StyleSheet } from 'react-native'
|
||||
|
||||
export default function AttendanceScreen() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>Attendance</Text>
|
||||
<Text style={styles.subtitle}>Track your gym visits</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#f5f5f5',
|
||||
},
|
||||
title: {
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 8,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 16,
|
||||
color: '#666',
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from 'react'
|
||||
import { View, Text, StyleSheet } from 'react-native'
|
||||
import { useRequireAuth } from '@/hooks/useRequireAuth'
|
||||
|
||||
export default function HomeScreen() {
|
||||
const { user } = useRequireAuth()
|
||||
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>Welcome back!</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
{user.firstName} {user.lastName}
|
||||
</Text>
|
||||
<Text style={styles.email}>{user.email}</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#f5f5f5',
|
||||
},
|
||||
title: {
|
||||
fontSize: 32,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 8,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 24,
|
||||
fontWeight: '600',
|
||||
marginBottom: 4,
|
||||
color: '#333',
|
||||
},
|
||||
email: {
|
||||
fontSize: 16,
|
||||
color: '#666',
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,116 @@
|
||||
import React from 'react'
|
||||
import { View, Text, StyleSheet, TouchableOpacity, Alert } from 'react-native'
|
||||
import { useAuth } from '@/contexts/AuthContext'
|
||||
import { useRouter } from 'expo-router'
|
||||
|
||||
export default function ProfileScreen() {
|
||||
const { user, logout } = useAuth()
|
||||
const router = useRouter()
|
||||
|
||||
const handleLogout = async () => {
|
||||
Alert.alert(
|
||||
'Logout',
|
||||
'Are you sure you want to logout?',
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{
|
||||
text: 'Logout',
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await logout()
|
||||
router.replace('/login')
|
||||
} catch (error) {
|
||||
Alert.alert('Error', 'Failed to logout')
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.profileCard}>
|
||||
<Text style={styles.title}>Profile</Text>
|
||||
<Text style={styles.name}>
|
||||
{user?.firstName} {user?.lastName}
|
||||
</Text>
|
||||
<Text style={styles.email}>{user?.email}</Text>
|
||||
{user?.phone && <Text style={styles.phone}>{user.phone}</Text>}
|
||||
|
||||
<View style={styles.roleBadge}>
|
||||
<Text style={styles.roleText}>
|
||||
{user?.role.charAt(0).toUpperCase() + user?.role.slice(1)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity style={styles.logoutButton} onPress={handleLogout}>
|
||||
<Text style={styles.logoutText}>Logout</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 20,
|
||||
backgroundColor: '#f5f5f5',
|
||||
},
|
||||
profileCard: {
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 12,
|
||||
padding: 24,
|
||||
alignItems: 'center',
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 3,
|
||||
},
|
||||
title: {
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 16,
|
||||
},
|
||||
name: {
|
||||
fontSize: 20,
|
||||
fontWeight: '600',
|
||||
marginBottom: 4,
|
||||
},
|
||||
email: {
|
||||
fontSize: 16,
|
||||
color: '#666',
|
||||
marginBottom: 4,
|
||||
},
|
||||
phone: {
|
||||
fontSize: 16,
|
||||
color: '#666',
|
||||
marginBottom: 16,
|
||||
},
|
||||
roleBadge: {
|
||||
backgroundColor: '#3b82f6',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 20,
|
||||
},
|
||||
roleText: {
|
||||
color: 'white',
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
},
|
||||
logoutButton: {
|
||||
backgroundColor: '#ef4444',
|
||||
paddingVertical: 14,
|
||||
borderRadius: 8,
|
||||
alignItems: 'center',
|
||||
marginTop: 24,
|
||||
},
|
||||
logoutText: {
|
||||
color: 'white',
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
import { AuthProvider } from '@/contexts/AuthContext'
|
||||
import { Stack } from 'expo-router'
|
||||
import { View, Text } from 'react-native'
|
||||
|
||||
export default function RootLayout() {
|
||||
return (
|
||||
<AuthProvider>
|
||||
<Stack>
|
||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="login" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="register" options={{ headerShown: false }} />
|
||||
</Stack>
|
||||
</AuthProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import React, { useState } from 'react'
|
||||
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from 'react-native'
|
||||
import { useRouter } from 'expo-router'
|
||||
import axios from 'axios'
|
||||
import * as SecureStore from 'expo-secure-store'
|
||||
import { API_BASE_URL, API_ENDPOINTS } from '../config/api'
|
||||
|
||||
export default function LoginScreen() {
|
||||
const [formData, setFormData] = useState({
|
||||
email: '',
|
||||
password: '',
|
||||
})
|
||||
const [loading, setLoading] = useState(false)
|
||||
const router = useRouter()
|
||||
|
||||
const handleLogin = async () => {
|
||||
if (!formData.email || !formData.password) {
|
||||
Alert.alert('Error', 'Please fill in all fields')
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
const response = await axios.post(`${API_BASE_URL}${API_ENDPOINTS.AUTH.LOGIN}`, formData)
|
||||
|
||||
if (response.data.user) {
|
||||
await SecureStore.setItemAsync('user', JSON.stringify(response.data.user))
|
||||
Alert.alert('Success', 'Login successful!', [
|
||||
{ text: 'OK', onPress: () => router.replace('/(tabs)') }
|
||||
])
|
||||
}
|
||||
} catch (error: any) {
|
||||
Alert.alert('Error', error.response?.data?.error || 'Login failed')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>Welcome Back</Text>
|
||||
<Text style={styles.subtitle}>Login to your FitAI account</Text>
|
||||
|
||||
<View style={styles.form}>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Email"
|
||||
value={formData.email}
|
||||
onChangeText={(text) => setFormData({ ...formData, email: text })}
|
||||
keyboardType="email-address"
|
||||
autoCapitalize="none"
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Password"
|
||||
value={formData.password}
|
||||
onChangeText={(text) => setFormData({ ...formData, password: text })}
|
||||
secureTextEntry
|
||||
/>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.button, loading && styles.buttonDisabled]}
|
||||
onPress={handleLogin}
|
||||
disabled={loading}
|
||||
>
|
||||
<Text style={styles.buttonText}>
|
||||
{loading ? 'Logging in...' : 'Login'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.linkButton}
|
||||
onPress={() => router.push('/register')}
|
||||
>
|
||||
<Text style={styles.linkText}>
|
||||
Don't have an account? Register
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#f5f5f5',
|
||||
padding: 20,
|
||||
},
|
||||
title: {
|
||||
fontSize: 32,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 8,
|
||||
color: '#333',
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 16,
|
||||
color: '#666',
|
||||
marginBottom: 32,
|
||||
},
|
||||
form: {
|
||||
width: '100%',
|
||||
maxWidth: 400,
|
||||
},
|
||||
input: {
|
||||
backgroundColor: 'white',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 8,
|
||||
marginBottom: 16,
|
||||
borderWidth: 1,
|
||||
borderColor: '#ddd',
|
||||
},
|
||||
button: {
|
||||
backgroundColor: '#3b82f6',
|
||||
paddingVertical: 14,
|
||||
borderRadius: 8,
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
buttonDisabled: {
|
||||
backgroundColor: '#9ca3af',
|
||||
},
|
||||
buttonText: {
|
||||
color: 'white',
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
linkButton: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
linkText: {
|
||||
color: '#3b82f6',
|
||||
fontSize: 14,
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,164 @@
|
||||
import React, { useState } from 'react'
|
||||
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from 'react-native'
|
||||
import { useRouter } from 'expo-router'
|
||||
import axios from 'axios'
|
||||
import { API_BASE_URL, API_ENDPOINTS } from '../config/api'
|
||||
|
||||
export default function RegisterScreen() {
|
||||
const [formData, setFormData] = useState({
|
||||
email: '',
|
||||
password: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
phone: '',
|
||||
})
|
||||
const [loading, setLoading] = useState(false)
|
||||
const router = useRouter()
|
||||
|
||||
const handleRegister = async () => {
|
||||
if (!formData.email || !formData.password || !formData.firstName || !formData.lastName) {
|
||||
Alert.alert('Error', 'Please fill in all required fields')
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
const response = await axios.post(`${API_BASE_URL}${API_ENDPOINTS.AUTH.REGISTER}`, formData)
|
||||
|
||||
if (response.status === 201) {
|
||||
Alert.alert('Success', 'Registration successful! Please login.', [
|
||||
{ text: 'OK', onPress: () => router.push('/login') }
|
||||
])
|
||||
}
|
||||
} catch (error: any) {
|
||||
Alert.alert('Error', error.response?.data?.error || 'Registration failed')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>Create Account</Text>
|
||||
<Text style={styles.subtitle}>Join FitAI today</Text>
|
||||
|
||||
<View style={styles.form}>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="First Name"
|
||||
value={formData.firstName}
|
||||
onChangeText={(text) => setFormData({ ...formData, firstName: text })}
|
||||
autoCapitalize="words"
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Last Name"
|
||||
value={formData.lastName}
|
||||
onChangeText={(text) => setFormData({ ...formData, lastName: text })}
|
||||
autoCapitalize="words"
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Email"
|
||||
value={formData.email}
|
||||
onChangeText={(text) => setFormData({ ...formData, email: text })}
|
||||
keyboardType="email-address"
|
||||
autoCapitalize="none"
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Phone (optional)"
|
||||
value={formData.phone}
|
||||
onChangeText={(text) => setFormData({ ...formData, phone: text })}
|
||||
keyboardType="phone-pad"
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Password"
|
||||
value={formData.password}
|
||||
onChangeText={(text) => setFormData({ ...formData, password: text })}
|
||||
secureTextEntry
|
||||
/>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.button, loading && styles.buttonDisabled]}
|
||||
onPress={handleRegister}
|
||||
disabled={loading}
|
||||
>
|
||||
<Text style={styles.buttonText}>
|
||||
{loading ? 'Creating Account...' : 'Create Account'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.linkButton}
|
||||
onPress={() => router.push('/login')}
|
||||
>
|
||||
<Text style={styles.linkText}>
|
||||
Already have an account? Login
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#f5f5f5',
|
||||
padding: 20,
|
||||
},
|
||||
title: {
|
||||
fontSize: 32,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 8,
|
||||
color: '#333',
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 16,
|
||||
color: '#666',
|
||||
marginBottom: 32,
|
||||
},
|
||||
form: {
|
||||
width: '100%',
|
||||
maxWidth: 400,
|
||||
},
|
||||
input: {
|
||||
backgroundColor: 'white',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 8,
|
||||
marginBottom: 16,
|
||||
borderWidth: 1,
|
||||
borderColor: '#ddd',
|
||||
},
|
||||
button: {
|
||||
backgroundColor: '#3b82f6',
|
||||
paddingVertical: 14,
|
||||
borderRadius: 8,
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
buttonDisabled: {
|
||||
backgroundColor: '#9ca3af',
|
||||
},
|
||||
buttonText: {
|
||||
color: 'white',
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
linkButton: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
linkText: {
|
||||
color: '#3b82f6',
|
||||
fontSize: 14,
|
||||
},
|
||||
})
|
||||
Reference in new issue
Block a user