diff --git a/frontend/src/screens/ChangePasswordScreen.tsx b/frontend/src/screens/ChangePasswordScreen.tsx new file mode 100644 index 00000000..c728e478 --- /dev/null +++ b/frontend/src/screens/ChangePasswordScreen.tsx @@ -0,0 +1,264 @@ +import React, { useState } from 'react'; +import { + View, + Text, + StyleSheet, + TouchableOpacity, + TextInput, + ScrollView, + Alert, + ActivityIndicator, +} from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { useAuth } from '../contexts/AuthContext'; + +export default function ChangePasswordScreen({ navigation }: any) { + const insets = useSafeAreaInsets(); + const { user } = useAuth(); + const [loading, setLoading] = useState(false); + const [currentPassword, setCurrentPassword] = useState(''); + const [newPassword, setNewPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [showCurrent, setShowCurrent] = useState(false); + const [showNew, setShowNew] = useState(false); + const [showConfirm, setShowConfirm] = useState(false); + + const handleChangePassword = async () => { + if (!currentPassword || !newPassword || !confirmPassword) { + Alert.alert('Error', 'Please fill in all fields'); + return; + } + + if (newPassword !== confirmPassword) { + Alert.alert('Error', 'New passwords do not match'); + return; + } + + if (newPassword.length < 8) { + Alert.alert('Error', 'Password must be at least 8 characters'); + return; + } + + setLoading(true); + try { + const backendUrl = process.env.EXPO_PUBLIC_BACKEND_URL || 'http://localhost:8001'; + const response = await fetch(`${backendUrl}/api/auth/change-password`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + user_id: user?.user_id, + current_password: currentPassword, + new_password: newPassword, + }), + }); + + if (response.ok) { + Alert.alert('Success', 'Password changed successfully!', [ + { text: 'OK', onPress: () => navigation.goBack() }, + ]); + } else { + Alert.alert('Error', 'Failed to change password'); + } + } catch (error) { + Alert.alert('Error', 'Network error occurred'); + } finally { + setLoading(false); + } + }; + + return ( + + + navigation.goBack()} style={styles.backButton}> + + + Change Password + + + + + + + + Password must be at least 8 characters long + + + + + Current Password + + + setShowCurrent(!showCurrent)} + style={styles.eyeButton} + > + + + + + + + New Password + + + setShowNew(!showNew)} + style={styles.eyeButton} + > + + + + + + + Confirm New Password + + + setShowConfirm(!showConfirm)} + style={styles.eyeButton} + > + + + + + + + {loading ? ( + + ) : ( + Change Password + )} + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#F8F9FA', + }, + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 16, + paddingVertical: 16, + backgroundColor: '#FFF', + borderBottomWidth: 1, + borderBottomColor: '#E5E7EB', + }, + backButton: { + width: 40, + height: 40, + borderRadius: 20, + backgroundColor: '#F3F4F6', + alignItems: 'center', + justifyContent: 'center', + }, + headerTitle: { + fontSize: 18, + fontWeight: '700', + color: '#1F2937', + }, + scrollContent: { + padding: 16, + paddingBottom: 80, + }, + infoBox: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: '#DBEAFE', + padding: 12, + borderRadius: 8, + marginBottom: 24, + }, + infoText: { + fontSize: 14, + color: '#1E40AF', + marginLeft: 8, + flex: 1, + }, + section: { + marginBottom: 20, + }, + label: { + fontSize: 14, + fontWeight: '600', + color: '#374151', + marginBottom: 8, + }, + passwordContainer: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: '#FFF', + borderRadius: 12, + borderWidth: 1, + borderColor: '#E5E7EB', + }, + passwordInput: { + flex: 1, + padding: 16, + fontSize: 16, + }, + eyeButton: { + padding: 12, + }, + saveButton: { + backgroundColor: '#EE2A35', + padding: 16, + borderRadius: 12, + alignItems: 'center', + marginTop: 24, + }, + saveButtonDisabled: { + opacity: 0.6, + }, + saveButtonText: { + fontSize: 16, + fontWeight: '600', + color: '#FFF', + }, +}); \ No newline at end of file diff --git a/frontend/src/screens/EditProfileScreen.tsx b/frontend/src/screens/EditProfileScreen.tsx new file mode 100644 index 00000000..4db5445c --- /dev/null +++ b/frontend/src/screens/EditProfileScreen.tsx @@ -0,0 +1,224 @@ +import React, { useState, useEffect } from 'react'; +import { + View, + Text, + StyleSheet, + TouchableOpacity, + TextInput, + ScrollView, + Alert, + ActivityIndicator, +} from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { useAuth } from '../contexts/AuthContext'; +import { API_ENDPOINTS } from '../config/api'; + +export default function EditProfileScreen({ navigation }: any) { + const insets = useSafeAreaInsets(); + const { user } = useAuth(); + const [loading, setLoading] = useState(false); + const [email, setEmail] = useState(''); + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + const [phone, setPhone] = useState(''); + const [walletAddress, setWalletAddress] = useState(''); + + useEffect(() => { + loadUserData(); + }, []); + + const loadUserData = async () => { + try { + const response = await fetch(`${API_ENDPOINTS.AUTH_USER(user?.user_id || '')}`); + if (response.ok) { + const data = await response.json(); + setEmail(data.email || ''); + setFirstName(data.first_name || ''); + setLastName(data.last_name || ''); + setPhone(data.phone || ''); + setWalletAddress(data.wallet_address || ''); + } + } catch (error) { + console.error('Error loading user data:', error); + } + }; + + const handleSave = async () => { + setLoading(true); + try { + const backendUrl = process.env.EXPO_PUBLIC_BACKEND_URL || 'http://localhost:8001'; + const response = await fetch(`${backendUrl}/api/auth/profile`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + user_id: user?.user_id, + email, + first_name: firstName, + last_name: lastName, + phone, + wallet_address: walletAddress, + }), + }); + + if (response.ok) { + Alert.alert('Success', 'Profile updated successfully!'); + navigation.goBack(); + } else { + Alert.alert('Error', 'Failed to update profile'); + } + } catch (error) { + Alert.alert('Error', 'Network error occurred'); + } finally { + setLoading(false); + } + }; + + return ( + + + navigation.goBack()} style={styles.backButton}> + + + Edit Profile + + + + + + First Name + + + + + Last Name + + + + + Email + + + + + Phone Number + + + + + Wallet Address + + + + + {loading ? ( + + ) : ( + Save Changes + )} + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#F8F9FA', + }, + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 16, + paddingVertical: 16, + backgroundColor: '#FFF', + borderBottomWidth: 1, + borderBottomColor: '#E5E7EB', + }, + backButton: { + width: 40, + height: 40, + borderRadius: 20, + backgroundColor: '#F3F4F6', + alignItems: 'center', + justifyContent: 'center', + }, + headerTitle: { + fontSize: 18, + fontWeight: '700', + color: '#1F2937', + }, + scrollContent: { + padding: 16, + paddingBottom: 80, + }, + section: { + marginBottom: 20, + }, + label: { + fontSize: 14, + fontWeight: '600', + color: '#374151', + marginBottom: 8, + }, + input: { + backgroundColor: '#FFF', + padding: 16, + borderRadius: 12, + fontSize: 16, + borderWidth: 1, + borderColor: '#E5E7EB', + }, + saveButton: { + backgroundColor: '#EE2A35', + padding: 16, + borderRadius: 12, + alignItems: 'center', + marginTop: 24, + }, + saveButtonDisabled: { + opacity: 0.6, + }, + saveButtonText: { + fontSize: 16, + fontWeight: '600', + color: '#FFF', + }, +}); \ No newline at end of file