sync profile api

This commit is contained in:
2025-07-03 22:36:28 +05:00
parent b56a96f0ff
commit 77e3ca0f18
4 changed files with 498 additions and 30 deletions

View File

@@ -59,17 +59,58 @@ export const AuthProvider = ({ children }) => {
const userData = await AsyncStorage.getItem('user_data');
if (token && userData) {
const user = JSON.parse(userData);
console.log('🔄 Found cached auth data, loading user...');
console.log('🎫 Token:', `${token.slice(0, 20)}...`);
console.log('👤 Cached user:', user);
dispatch({
type: 'LOGIN_SUCCESS',
payload: {
token,
user: JSON.parse(userData),
},
payload: { token, user },
});
// Try to refresh profile data in background
console.log('🔄 Refreshing profile in background...');
try {
const freshProfileData = await authService.getProfile();
console.log('👤 Fresh profile data:', freshProfileData);
// Validate fresh data
if (freshProfileData.name && freshProfileData.phone) {
// Update cached data
await AsyncStorage.setItem('user_data', JSON.stringify(freshProfileData));
console.log('✅ Profile refreshed successfully on app startup');
// Update state with fresh data
dispatch({
type: 'LOGIN_SUCCESS',
payload: { token, user: freshProfileData },
});
} else {
console.warn('⚠️ Fresh profile data is invalid, keeping cached data');
}
} catch (profileError) {
console.warn('⚠️ Could not refresh profile on startup:', profileError.message);
// Check if it's a token expiration error
if (profileError.message.includes('Session expired')) {
console.log('🔓 Token expired, clearing auth data');
await AsyncStorage.removeItem('auth_token');
await AsyncStorage.removeItem('user_data');
dispatch({ type: 'LOGOUT' });
return;
}
// Otherwise, continue with cached data
console.log('📦 Using cached user data');
}
} else {
console.log('🔍 No cached auth data found');
dispatch({ type: 'LOADING', payload: false });
}
} catch (error) {
console.error('💥 Error checking auth status:', error.message);
dispatch({ type: 'LOADING', payload: false });
}
};
@@ -117,23 +158,47 @@ export const AuthProvider = ({ children }) => {
}
dispatch({ type: 'LOADING', payload: true });
const response = await authService.verify(state.pendingVerification.phone, code);
// Assuming the API returns a token after successful verification
// You might need to adjust this based on your actual API response
const token = response.token || 'dummy_token_for_demo';
const user = response.user || { phone: state.pendingVerification.phone };
// Step 1: Verify the code and get the token
const verificationResponse = await authService.verify(state.pendingVerification.phone, code);
// Store auth data
console.log('🔍 Verification Response:', verificationResponse);
// Extract data from your API response structure
if (!verificationResponse.success) {
dispatch({ type: 'LOADING', payload: false });
return { success: false, error: verificationResponse.message || 'Verification failed' };
}
const token = verificationResponse.token;
const basicUserData = verificationResponse.user;
if (!token) {
dispatch({ type: 'LOADING', payload: false });
return { success: false, error: 'No access token received from verification' };
}
console.log('✅ Token received:', `${token.slice(0, 20)}...`);
console.log('👤 Basic user data:', basicUserData);
// Step 2: Cache auth data (token and user data from verification)
await AsyncStorage.setItem('auth_token', token);
await AsyncStorage.setItem('user_data', JSON.stringify(user));
await AsyncStorage.setItem('user_data', JSON.stringify(basicUserData));
console.log('✅ Auth data cached successfully');
console.log('🎫 Token stored:', `${token.slice(0, 20)}...`);
console.log('👤 User data cached:', basicUserData);
dispatch({
type: 'LOGIN_SUCCESS',
payload: { token, user },
payload: { token, user: basicUserData },
});
return { success: true, message: response.message };
return {
success: true,
message: verificationResponse.message,
user: basicUserData
};
} catch (error) {
dispatch({ type: 'LOADING', payload: false });
return { success: false, error: error.message };
@@ -154,6 +219,66 @@ export const AuthProvider = ({ children }) => {
dispatch({ type: 'CLEAR_PENDING_VERIFICATION' });
};
const updateUserProfile = async (profileData) => {
try {
const response = await authService.updateProfile(profileData);
const updatedUser = { ...state.user, ...profileData };
// Update AsyncStorage
await AsyncStorage.setItem('user_data', JSON.stringify(updatedUser));
// Update state
dispatch({
type: 'LOGIN_SUCCESS',
payload: {
token: state.token,
user: updatedUser,
},
});
return { success: true, message: response.message };
} catch (error) {
return { success: false, error: error.message };
}
};
const fetchUserProfile = async () => {
try {
console.log('🔄 Manually fetching user profile...');
const profileData = await authService.getProfile();
console.log('👤 Fetched profile data:', profileData);
// Validate profile data structure
if (!profileData.name || !profileData.phone) {
throw new Error('Invalid profile data structure received');
}
// Update AsyncStorage
await AsyncStorage.setItem('user_data', JSON.stringify(profileData));
// Update state
dispatch({
type: 'LOGIN_SUCCESS',
payload: {
token: state.token,
user: profileData,
},
});
console.log('✅ Profile updated successfully');
return { success: true, user: profileData };
} catch (error) {
console.error('💥 Failed to fetch profile:', error.message);
// If unauthorized, logout user
if (error.message.includes('Session expired')) {
console.log('🔓 Session expired during profile fetch, logging out');
await logout();
}
return { success: false, error: error.message };
}
};
const value = {
...state,
login,
@@ -161,6 +286,8 @@ export const AuthProvider = ({ children }) => {
verify,
logout,
clearPendingVerification,
updateUserProfile,
fetchUserProfile,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import {
View,
Text,
@@ -7,14 +7,47 @@ import {
ScrollView,
TouchableOpacity,
Alert,
ActivityIndicator,
RefreshControl,
} from 'react-native';
import { StatusBar } from 'expo-status-bar';
import { Ionicons } from '@expo/vector-icons';
import { useAuth } from '../../contexts/AuthContext';
import apiService from '../../services/apiService';
import { COLORS } from '../../constants/colors';
const ProfileScreen = () => {
const { user, logout } = useAuth();
const { user, logout, fetchUserProfile } = useAuth();
const [isLoading, setIsLoading] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const [profileData, setProfileData] = useState(null);
useEffect(() => {
loadProfileData();
}, []);
const loadProfileData = async () => {
if (isLoading) return;
setIsLoading(true);
try {
// Fetch fresh profile data from API
const result = await fetchUserProfile();
if (result.success) {
setProfileData(result.user);
}
} catch (error) {
console.error('Error loading profile:', error);
} finally {
setIsLoading(false);
}
};
const handleRefresh = async () => {
setIsRefreshing(true);
await loadProfileData();
setIsRefreshing(false);
};
const profileSections = [
{
@@ -53,8 +86,100 @@ const ProfileScreen = () => {
];
const handleProfileItemPress = (item) => {
console.log('Profile item pressed:', item.title);
// Handle navigation or action here
switch (item.id) {
case 1: // Personal Information
handleEditPersonalInfo();
break;
case 2: // Contact Information
handleEditContactInfo();
break;
case 3: // Change Password
handleChangePassword();
break;
case 4: // Notifications
handleNotificationSettings();
break;
case 5: // Security
handleSecuritySettings();
break;
default:
Alert.alert('Üns beriň', 'Bu funksiýa entek işlenok');
}
};
const handleEditPersonalInfo = () => {
// Navigate to edit personal info screen
Alert.alert('Şahsy maglumatlar', 'Şahsy maglumatlar sahypasy açylýar...');
};
const handleEditContactInfo = () => {
// Navigate to edit contact info screen
Alert.alert('Aragatnaşyk', 'Aragatnaşyk maglumatlar sahypasy açylýar...');
};
const handleChangePassword = () => {
Alert.prompt(
'Paroly üýtget',
'Häzirki parolyňyzy giriziň',
[
{ text: 'Ýatyr', style: 'cancel' },
{
text: 'Dowam et',
onPress: (currentPassword) => {
if (currentPassword) {
promptForNewPassword(currentPassword);
}
}
}
],
'secure-text'
);
};
const promptForNewPassword = (currentPassword) => {
Alert.prompt(
'Täze parol',
'Täze parolyňyzy giriziň',
[
{ text: 'Ýatyr', style: 'cancel' },
{
text: 'Üýtget',
onPress: (newPassword) => {
if (newPassword && newPassword.length >= 6) {
changePassword(currentPassword, newPassword);
} else {
Alert.alert('Ýalňyşlyk', 'Parol azyndan 6 harp bolmaly');
}
}
}
],
'secure-text'
);
};
const changePassword = async (currentPassword, newPassword) => {
try {
setIsLoading(true);
const result = await apiService.changePassword(currentPassword, newPassword);
if (result.success) {
Alert.alert('Üstünlik', 'Parol üstünlikli üýtgedildi');
} else {
Alert.alert('Ýalňyşlyk', result.error || 'Paroly üýtgetmek amala aşmady');
}
} catch (error) {
Alert.alert('Ýalňyşlyk', error.message || 'Paroly üýtgetmek amala aşmady');
} finally {
setIsLoading(false);
}
};
const handleNotificationSettings = () => {
Alert.alert('Bildirişler', 'Bildiriş sazlamalary sahypasy açylýar...');
};
const handleSecuritySettings = () => {
Alert.alert('Howpsuzlyk', 'Howpsuzlyk sazlamalary sahypasy açylýar...');
};
const handleLogout = () => {
@@ -78,9 +203,12 @@ const ProfileScreen = () => {
const formatPhoneNumber = (phone) => {
if (!phone) return '';
const phoneStr = phone.toString();
return `+993 ${phoneStr.slice(0, 2)} ${phoneStr.slice(2, 5)} ${phoneStr.slice(5)}`;
return `+993 ${phoneStr.slice(0, 2)} ${phoneStr.slice(2, 4)}-${phoneStr.slice(4, 6)}-${phoneStr.slice(6)}`;
};
// Use profile data from API or fallback to cached user data
const currentUser = profileData || user;
return (
<SafeAreaView style={styles.container}>
<StatusBar style="dark" />
@@ -89,19 +217,36 @@ const ProfileScreen = () => {
<Text style={styles.headerTitle}>Profil</Text>
</View>
<ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
<ScrollView
style={styles.scrollView}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={handleRefresh}
colors={[COLORS.primary]}
tintColor={COLORS.primary}
/>
}
>
{/* User Info Card */}
<View style={styles.userCard}>
<View style={styles.userAvatar}>
<Text style={styles.userInitials}>
{user?.name ? user.name.split(' ').map(n => n[0]).join('').toUpperCase() : 'U'}
{currentUser?.name ? currentUser.name.split(' ').map(n => n[0]).join('').toUpperCase() : 'U'}
</Text>
</View>
<View style={styles.userInfo}>
<Text style={styles.userName}>{user?.name || 'Ulanyjy'}</Text>
<Text style={styles.userPhone}>{formatPhoneNumber(user?.phone)}</Text>
<Text style={styles.userName}>
{currentUser?.name || 'Ulanyjy'}
{isLoading && <ActivityIndicator size="small" color={COLORS.primary} style={{ marginLeft: 8 }} />}
</Text>
<Text style={styles.userPhone}>{formatPhoneNumber(currentUser?.phone)}</Text>
{currentUser?.email && (
<Text style={styles.userEmail}>{currentUser.email}</Text>
)}
</View>
<TouchableOpacity style={styles.editButton}>
<TouchableOpacity style={styles.editButton} onPress={handleEditPersonalInfo}>
<Ionicons name="pencil" size={20} color={COLORS.primary} />
</TouchableOpacity>
</View>
@@ -219,6 +364,11 @@ const styles = StyleSheet.create({
fontSize: 16,
color: COLORS.textSecondary,
},
userEmail: {
fontSize: 14,
color: COLORS.textSecondary,
marginTop: 2,
},
editButton: {
padding: 8,
},

View File

@@ -0,0 +1,82 @@
import authService from './authService';
class ApiService {
// Profile methods
async getProfile() {
return authService.getProfile();
}
async updateProfile(data) {
return authService.updateProfile(data);
}
async changePassword(currentPassword, newPassword) {
return authService.changePassword(currentPassword, newPassword);
}
async deleteAccount() {
return authService.deleteAccount();
}
// Balance and transactions
async getBalance() {
return authService.getBalance();
}
async getTransactions(page = 1, limit = 20) {
return authService.getTransactions(page, limit);
}
// Transfer and payments
async transferMoney(data) {
return authService.transferMoney(data);
}
async payBill(data) {
return authService.payBill(data);
}
// Cards management
async getCards() {
return authService.getCards();
}
async addCard(data) {
return authService.addCard(data);
}
async blockCard(cardId) {
return authService.blockCard(cardId);
}
async unblockCard(cardId) {
return authService.unblockCard(cardId);
}
// Utility methods for common operations
async getUserDashboardData() {
try {
const [balance, transactions, cards] = await Promise.all([
this.getBalance().catch(() => ({ balance: 0 })),
this.getTransactions(1, 5).catch(() => ({ transactions: [] })),
this.getCards().catch(() => ({ cards: [] }))
]);
return {
success: true,
data: {
balance: balance.balance || 0,
recentTransactions: transactions.transactions || [],
cards: cards.cards || []
}
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
}
export default new ApiService();

View File

@@ -1,30 +1,87 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { API_CONFIG } from '../constants/api';
class AuthService {
async makeRequest(endpoint, data, token = null) {
async getStoredToken() {
try {
return await AsyncStorage.getItem('auth_token');
} catch (error) {
return null;
}
}
async makeRequest(endpoint, data, requiresAuth = false, method = 'POST') {
const fullUrl = `${API_CONFIG.BASE_URL}${endpoint}`;
const requestId = Math.random().toString(36).substr(2, 9);
try {
const headers = {
...API_CONFIG.HEADERS,
};
// Auto-include token for authenticated requests
if (requiresAuth) {
const token = await this.getStoredToken();
if (token) {
headers.Authorization = `Bearer ${token}`;
}
}
const response = await fetch(`${API_CONFIG.BASE_URL}${endpoint}`, {
method: 'POST',
headers,
body: JSON.stringify(data),
// Log request details
console.log(`\n🚀 [${requestId}] API REQUEST`);
console.log(`📍 URL: ${method} ${fullUrl}`);
console.log(`🔐 Auth Required: ${requiresAuth}`);
console.log(`📝 Headers:`, {
...headers,
Authorization: headers.Authorization ? `Bearer ${headers.Authorization.slice(-10)}...` : 'None'
});
if (data) {
console.log(`📦 Body:`, data);
}
console.log(`⏰ Time: ${new Date().toISOString()}`);
const startTime = Date.now();
const response = await fetch(fullUrl, {
method,
headers,
body: data ? JSON.stringify(data) : undefined,
});
const endTime = Date.now();
const duration = endTime - startTime;
const result = await response.json();
// Log response details
console.log(`\n✅ [${requestId}] API RESPONSE`);
console.log(`📊 Status: ${response.status} ${response.statusText}`);
console.log(`⏱️ Duration: ${duration}ms`);
console.log(`📄 Response:`, result);
if (!response.ok) {
console.log(`❌ [${requestId}] API ERROR`);
console.log(`🔴 Status: ${response.status}`);
console.log(`💬 Error Message:`, result.message || 'Unknown error');
// Handle token expiration
if (response.status === 401 && requiresAuth) {
console.log(`🔓 [${requestId}] TOKEN EXPIRED - Clearing storage`);
await AsyncStorage.removeItem('auth_token');
await AsyncStorage.removeItem('user_data');
throw new Error('Session expired. Please login again.');
}
throw new Error(result.message || 'An error occurred');
}
console.log(`✨ [${requestId}] REQUEST COMPLETED SUCCESSFULLY\n`);
return result;
} catch (error) {
console.log(`\n💥 [${requestId}] API REQUEST FAILED`);
console.log(`📍 URL: ${method} ${fullUrl}`);
console.log(`🔴 Error:`, error.message);
console.log(`📅 Time: ${new Date().toISOString()}\n`);
throw new Error(error.message || 'Network error');
}
}
@@ -50,6 +107,58 @@ class AuthService {
code: parseInt(code),
});
}
// Authenticated API methods
async getProfile() {
return this.makeRequest('/profile', null, true, 'GET');
}
async updateProfile(data) {
return this.makeRequest('/profile', data, true, 'PUT');
}
async getTransactions(page = 1, limit = 20) {
return this.makeRequest(`/user/transactions?page=${page}&limit=${limit}`, null, true, 'GET');
}
async getBalance() {
return this.makeRequest('/user/balance', null, true, 'GET');
}
async transferMoney(data) {
return this.makeRequest('/user/transfer', data, true);
}
async payBill(data) {
return this.makeRequest('/user/pay-bill', data, true);
}
async getCards() {
return this.makeRequest('/user/cards', null, true, 'GET');
}
async addCard(data) {
return this.makeRequest('/user/cards', data, true);
}
async blockCard(cardId) {
return this.makeRequest(`/user/cards/${cardId}/block`, null, true);
}
async unblockCard(cardId) {
return this.makeRequest(`/user/cards/${cardId}/unblock`, null, true);
}
async changePassword(currentPassword, newPassword) {
return this.makeRequest('/user/change-password', {
current_password: currentPassword,
new_password: newPassword,
}, true);
}
async deleteAccount() {
return this.makeRequest('/user/delete-account', null, true, 'DELETE');
}
}
export default new AuthService();