sync profile api
This commit is contained in:
@@ -59,17 +59,58 @@ export const AuthProvider = ({ children }) => {
|
|||||||
const userData = await AsyncStorage.getItem('user_data');
|
const userData = await AsyncStorage.getItem('user_data');
|
||||||
|
|
||||||
if (token && userData) {
|
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({
|
dispatch({
|
||||||
type: 'LOGIN_SUCCESS',
|
type: 'LOGIN_SUCCESS',
|
||||||
payload: {
|
payload: { token, user },
|
||||||
token,
|
|
||||||
user: JSON.parse(userData),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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 {
|
} else {
|
||||||
|
console.log('🔍 No cached auth data found');
|
||||||
dispatch({ type: 'LOADING', payload: false });
|
dispatch({ type: 'LOADING', payload: false });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('💥 Error checking auth status:', error.message);
|
||||||
dispatch({ type: 'LOADING', payload: false });
|
dispatch({ type: 'LOADING', payload: false });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -117,23 +158,47 @@ export const AuthProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispatch({ type: 'LOADING', payload: true });
|
dispatch({ type: 'LOADING', payload: true });
|
||||||
const response = await authService.verify(state.pendingVerification.phone, code);
|
|
||||||
|
|
||||||
// Assuming the API returns a token after successful verification
|
// Step 1: Verify the code and get the token
|
||||||
// You might need to adjust this based on your actual API response
|
const verificationResponse = await authService.verify(state.pendingVerification.phone, code);
|
||||||
const token = response.token || 'dummy_token_for_demo';
|
|
||||||
const user = response.user || { phone: state.pendingVerification.phone };
|
|
||||||
|
|
||||||
// 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('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({
|
dispatch({
|
||||||
type: 'LOGIN_SUCCESS',
|
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) {
|
} catch (error) {
|
||||||
dispatch({ type: 'LOADING', payload: false });
|
dispatch({ type: 'LOADING', payload: false });
|
||||||
return { success: false, error: error.message };
|
return { success: false, error: error.message };
|
||||||
@@ -154,6 +219,66 @@ export const AuthProvider = ({ children }) => {
|
|||||||
dispatch({ type: 'CLEAR_PENDING_VERIFICATION' });
|
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 = {
|
const value = {
|
||||||
...state,
|
...state,
|
||||||
login,
|
login,
|
||||||
@@ -161,6 +286,8 @@ export const AuthProvider = ({ children }) => {
|
|||||||
verify,
|
verify,
|
||||||
logout,
|
logout,
|
||||||
clearPendingVerification,
|
clearPendingVerification,
|
||||||
|
updateUserProfile,
|
||||||
|
fetchUserProfile,
|
||||||
};
|
};
|
||||||
|
|
||||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
@@ -7,14 +7,47 @@ import {
|
|||||||
ScrollView,
|
ScrollView,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
Alert,
|
Alert,
|
||||||
|
ActivityIndicator,
|
||||||
|
RefreshControl,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
import { Ionicons } from '@expo/vector-icons';
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
import { useAuth } from '../../contexts/AuthContext';
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
|
import apiService from '../../services/apiService';
|
||||||
import { COLORS } from '../../constants/colors';
|
import { COLORS } from '../../constants/colors';
|
||||||
|
|
||||||
const ProfileScreen = () => {
|
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 = [
|
const profileSections = [
|
||||||
{
|
{
|
||||||
@@ -53,8 +86,100 @@ const ProfileScreen = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const handleProfileItemPress = (item) => {
|
const handleProfileItemPress = (item) => {
|
||||||
console.log('Profile item pressed:', item.title);
|
switch (item.id) {
|
||||||
// Handle navigation or action here
|
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 = () => {
|
const handleLogout = () => {
|
||||||
@@ -78,9 +203,12 @@ const ProfileScreen = () => {
|
|||||||
const formatPhoneNumber = (phone) => {
|
const formatPhoneNumber = (phone) => {
|
||||||
if (!phone) return '';
|
if (!phone) return '';
|
||||||
const phoneStr = phone.toString();
|
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 (
|
return (
|
||||||
<SafeAreaView style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
<StatusBar style="dark" />
|
<StatusBar style="dark" />
|
||||||
@@ -89,19 +217,36 @@ const ProfileScreen = () => {
|
|||||||
<Text style={styles.headerTitle}>Profil</Text>
|
<Text style={styles.headerTitle}>Profil</Text>
|
||||||
</View>
|
</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 */}
|
{/* User Info Card */}
|
||||||
<View style={styles.userCard}>
|
<View style={styles.userCard}>
|
||||||
<View style={styles.userAvatar}>
|
<View style={styles.userAvatar}>
|
||||||
<Text style={styles.userInitials}>
|
<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>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.userInfo}>
|
<View style={styles.userInfo}>
|
||||||
<Text style={styles.userName}>{user?.name || 'Ulanyjy'}</Text>
|
<Text style={styles.userName}>
|
||||||
<Text style={styles.userPhone}>{formatPhoneNumber(user?.phone)}</Text>
|
{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>
|
</View>
|
||||||
<TouchableOpacity style={styles.editButton}>
|
<TouchableOpacity style={styles.editButton} onPress={handleEditPersonalInfo}>
|
||||||
<Ionicons name="pencil" size={20} color={COLORS.primary} />
|
<Ionicons name="pencil" size={20} color={COLORS.primary} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
@@ -219,6 +364,11 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: COLORS.textSecondary,
|
color: COLORS.textSecondary,
|
||||||
},
|
},
|
||||||
|
userEmail: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
editButton: {
|
editButton: {
|
||||||
padding: 8,
|
padding: 8,
|
||||||
},
|
},
|
||||||
|
|||||||
82
src/services/apiService.js
Normal file
82
src/services/apiService.js
Normal 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();
|
||||||
@@ -1,30 +1,87 @@
|
|||||||
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import { API_CONFIG } from '../constants/api';
|
import { API_CONFIG } from '../constants/api';
|
||||||
|
|
||||||
class AuthService {
|
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 {
|
try {
|
||||||
const headers = {
|
const headers = {
|
||||||
...API_CONFIG.HEADERS,
|
...API_CONFIG.HEADERS,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (token) {
|
// Auto-include token for authenticated requests
|
||||||
headers.Authorization = `Bearer ${token}`;
|
if (requiresAuth) {
|
||||||
|
const token = await this.getStoredToken();
|
||||||
|
if (token) {
|
||||||
|
headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${API_CONFIG.BASE_URL}${endpoint}`, {
|
// Log request details
|
||||||
method: 'POST',
|
console.log(`\n🚀 [${requestId}] API REQUEST`);
|
||||||
headers,
|
console.log(`📍 URL: ${method} ${fullUrl}`);
|
||||||
body: JSON.stringify(data),
|
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();
|
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) {
|
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');
|
throw new Error(result.message || 'An error occurred');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`✨ [${requestId}] REQUEST COMPLETED SUCCESSFULLY\n`);
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} 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');
|
throw new Error(error.message || 'Network error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,6 +107,58 @@ class AuthService {
|
|||||||
code: parseInt(code),
|
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();
|
export default new AuthService();
|
||||||
Reference in New Issue
Block a user