1137 lines
31 KiB
React
1137 lines
31 KiB
React
import axios from 'axios';
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
import { View, Text } from 'react-native';
|
|
import { getApiBaseUrl } from './config';
|
|
import { STORAGE_KEYS } from './constants/storageKeys';
|
|
|
|
// Utility function to simulate network delay
|
|
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
// Mock products data for development
|
|
const mockProducts = [
|
|
{
|
|
id: 1,
|
|
title: "Wireless Headphones",
|
|
description: "High-quality wireless headphones with noise cancellation",
|
|
price: 199.99,
|
|
rating: 4.5,
|
|
category: "Electronics",
|
|
image: "headphones.jpg"
|
|
},
|
|
{
|
|
id: 2,
|
|
title: "Smart Watch",
|
|
description: "Latest smart watch with fitness tracking",
|
|
price: 299.99,
|
|
rating: 4.2,
|
|
category: "Electronics",
|
|
image: "smartwatch.jpg"
|
|
},
|
|
{
|
|
id: 3,
|
|
title: "Running Shoes",
|
|
description: "Comfortable running shoes for all terrains",
|
|
price: 89.99,
|
|
rating: 4.7,
|
|
category: "Sports",
|
|
image: "shoes.jpg"
|
|
},
|
|
{
|
|
id: 4,
|
|
title: "Backpack",
|
|
description: "Durable backpack with laptop compartment",
|
|
price: 49.99,
|
|
rating: 4.3,
|
|
category: "Accessories",
|
|
image: "backpack.jpg"
|
|
},
|
|
{
|
|
id: 5,
|
|
title: "Coffee Maker",
|
|
description: "Programmable coffee maker with timer",
|
|
price: 79.99,
|
|
rating: 4.1,
|
|
category: "Home",
|
|
image: "coffeemaker.jpg"
|
|
}
|
|
];
|
|
|
|
// Dummy component to fix Expo Router error
|
|
export default function Api() {
|
|
return (
|
|
<View style={{ display: 'none' }}>
|
|
<Text>API Service - This component is only to prevent Expo Router errors</Text>
|
|
</View>
|
|
);
|
|
}
|
|
// API Configuration
|
|
export const MEDIA_BASE_URL = getApiBaseUrl();
|
|
export const BASE_URL = `${MEDIA_BASE_URL}/api`;
|
|
|
|
// Create axios instance with default config
|
|
const api = axios.create({
|
|
baseURL: BASE_URL,
|
|
withCredentials: true,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
},
|
|
// Ensure cookies are included in cross-origin requests
|
|
xsrfCookieName: 'csrftoken',
|
|
xsrfHeaderName: 'X-CSRFToken',
|
|
});
|
|
|
|
// Function to update base URL
|
|
export const updateBaseURL = (newURL) => {
|
|
api.defaults.baseURL = newURL;
|
|
};
|
|
|
|
// Function to get CSRF token
|
|
export const getCSRFToken = async () => {
|
|
try {
|
|
// First check if we already have a token stored
|
|
const storedToken = await getStoredCSRFToken();
|
|
if (storedToken) {
|
|
return storedToken;
|
|
}
|
|
|
|
// If not, fetch a new token
|
|
const response = await api.get('/csrf-token/');
|
|
|
|
// Extract token from response
|
|
let csrfToken = null;
|
|
if (response.data && response.data.csrfToken) {
|
|
csrfToken = response.data.csrfToken;
|
|
} else if (response.headers && response.headers['x-csrf-token']) {
|
|
csrfToken = response.headers['x-csrf-token'];
|
|
}
|
|
|
|
// Store the token if we got one
|
|
if (csrfToken) {
|
|
await AsyncStorage.setItem(STORAGE_KEYS.CSRF_TOKEN, csrfToken);
|
|
return csrfToken;
|
|
}
|
|
|
|
console.warn('No CSRF token found in response');
|
|
return null;
|
|
} catch (error) {
|
|
console.error('Error fetching CSRF token:', error);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
// Function to get stored CSRF token
|
|
export const getStoredCSRFToken = async () => {
|
|
try {
|
|
return await AsyncStorage.getItem(STORAGE_KEYS.CSRF_TOKEN);
|
|
} catch (error) {
|
|
console.error('Error getting stored CSRF token:', error);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
// Initialize API and fetch CSRF token
|
|
export const initializeAPI = async () => {
|
|
try {
|
|
// Get stored auth token
|
|
const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
|
|
if (token) {
|
|
api.defaults.headers.common['Authorization'] = `Token ${token}`;
|
|
}
|
|
|
|
// Get CSRF token
|
|
await getCSRFToken();
|
|
} catch (error) {
|
|
console.error('Error initializing API:', error);
|
|
}
|
|
};
|
|
|
|
// Request interceptor
|
|
api.interceptors.request.use(
|
|
async (config) => {
|
|
try {
|
|
// Add auth token to headers
|
|
const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
|
|
if (token) {
|
|
config.headers['Authorization'] = `Token ${token}`;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error in request interceptor:', error);
|
|
}
|
|
return config;
|
|
},
|
|
(error) => {
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
|
|
// Response interceptor
|
|
api.interceptors.response.use(
|
|
(response) => response,
|
|
async (error) => {
|
|
if (error.response?.status === 401) {
|
|
try {
|
|
await AsyncStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN);
|
|
await AsyncStorage.removeItem(STORAGE_KEYS.USER_DATA);
|
|
delete api.defaults.headers.common['Authorization'];
|
|
} catch (storageError) {
|
|
console.error('Error clearing auth data:', storageError);
|
|
}
|
|
}
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
|
|
// Auth functions
|
|
export const login = async (credentials) => {
|
|
try {
|
|
const response = await api.post('/token-auth/', credentials);
|
|
if (response.data.token) {
|
|
// Set the auth token in the API instance
|
|
api.defaults.headers.common['Authorization'] = `Token ${response.data.token}`;
|
|
// Store the token and user data
|
|
await AsyncStorage.setItem(STORAGE_KEYS.AUTH_TOKEN, response.data.token);
|
|
if (response.data.user_id) {
|
|
// Get user data
|
|
const userResponse = await api.get(`/users/${response.data.user_id}/`);
|
|
if (userResponse.data) {
|
|
await AsyncStorage.setItem(STORAGE_KEYS.USER_DATA, JSON.stringify(userResponse.data));
|
|
}
|
|
}
|
|
}
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error('Login error:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const register = async (userData) => {
|
|
try {
|
|
// First, get a CSRF token
|
|
const csrfToken = await getCSRFToken();
|
|
|
|
// Make the registration request with the CSRF token in headers
|
|
const response = await api.post('/auth/register/', userData, {
|
|
headers: {
|
|
'X-CSRFToken': csrfToken
|
|
}
|
|
});
|
|
|
|
// If registration is successful and we get a token, store it
|
|
if (response.data && response.data.token) {
|
|
// Set the auth token in the API instance
|
|
api.defaults.headers.common['Authorization'] = `Token ${response.data.token}`;
|
|
// Store the token
|
|
await AsyncStorage.setItem(STORAGE_KEYS.AUTH_TOKEN, response.data.token);
|
|
|
|
// If we have user data, store it
|
|
if (response.data.user_id) {
|
|
const userData = {
|
|
id: response.data.user_id,
|
|
username: response.data.username,
|
|
email: response.data.email,
|
|
first_name: response.data.first_name,
|
|
last_name: response.data.last_name
|
|
};
|
|
await AsyncStorage.setItem(STORAGE_KEYS.USER_DATA, JSON.stringify(userData));
|
|
}
|
|
}
|
|
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error('Registration error:', error.response?.data || error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const logout = async () => {
|
|
try {
|
|
await AsyncStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN);
|
|
await AsyncStorage.removeItem(STORAGE_KEYS.USER_DATA);
|
|
delete api.defaults.headers.common['Authorization'];
|
|
} catch (error) {
|
|
console.error('Logout error:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// User functions
|
|
export const getCurrentUser = async () => {
|
|
try {
|
|
// Check if we have a token
|
|
const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
|
|
if (!token) return null;
|
|
|
|
// Get user data from API
|
|
const response = await api.get('/users/me/');
|
|
|
|
// Store updated data
|
|
if (response.data) {
|
|
await AsyncStorage.setItem(STORAGE_KEYS.USER_DATA, JSON.stringify(response.data));
|
|
}
|
|
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error('Error getting current user:', error);
|
|
|
|
// Fallback to cached data
|
|
const userData = await AsyncStorage.getItem(STORAGE_KEYS.USER_DATA);
|
|
return userData ? JSON.parse(userData) : null;
|
|
}
|
|
};
|
|
|
|
export const getUserData = async () => {
|
|
try {
|
|
// First try to get from API
|
|
const currentUser = await getCurrentUser();
|
|
if (currentUser) return currentUser;
|
|
|
|
// If API fails, try to get from storage
|
|
const userData = await AsyncStorage.getItem(STORAGE_KEYS.USER_DATA);
|
|
return userData ? JSON.parse(userData) : null;
|
|
} catch (error) {
|
|
console.error('Error getting user data:', error);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
export const updateUserProfile = async (profileData) => {
|
|
try {
|
|
const response = await api.post('/users/update_profile/', profileData);
|
|
if (response.data) {
|
|
await AsyncStorage.setItem(STORAGE_KEYS.USER_DATA, JSON.stringify(response.data));
|
|
}
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error('Profile update error:', error.response?.data || error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const updateProfilePicture = async (imageUri) => {
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append('profile_picture', {
|
|
uri: imageUri,
|
|
type: 'image/jpeg',
|
|
name: 'profile_picture.jpg',
|
|
});
|
|
|
|
const response = await api.post('/users/update_profile_picture/', formData, {
|
|
headers: {
|
|
'Content-Type': 'multipart/form-data',
|
|
'Accept': 'application/json',
|
|
},
|
|
});
|
|
|
|
if (response.data && response.data.profile_picture) {
|
|
await AsyncStorage.setItem(STORAGE_KEYS.USER_DATA, JSON.stringify(response.data));
|
|
}
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error('Profile picture update error:', error.response?.data || error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const getGenderOptions = async () => {
|
|
try {
|
|
const response = await api.get('/users/gender_options/');
|
|
// Transform the response to match the backend values
|
|
return response.data.map(option => ({
|
|
value: option.value.charAt(0).toUpperCase() + option.value.slice(1),
|
|
label: option.label
|
|
}));
|
|
} catch (error) {
|
|
// Return default options if API fails
|
|
return [
|
|
{ value: 'Male', label: 'Male' },
|
|
{ value: 'Female', label: 'Female' },
|
|
{ value: 'Other', label: 'Other' }
|
|
];
|
|
}
|
|
};
|
|
|
|
// Cart functions
|
|
export const getCart = async () => {
|
|
try {
|
|
const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
|
|
if (!token) {
|
|
// If not logged in, get from local storage
|
|
const cartData = await AsyncStorage.getItem(STORAGE_KEYS.CART);
|
|
return cartData ? JSON.parse(cartData) : { items: [] };
|
|
}
|
|
|
|
// Set the auth token in headers
|
|
api.defaults.headers.common['Authorization'] = `Token ${token}`;
|
|
|
|
const { data } = await api.get('/cart/items/');
|
|
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error getting cart:', error);
|
|
|
|
if (error.response?.status === 401) {
|
|
// Clear auth data on 401
|
|
await AsyncStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN);
|
|
delete api.defaults.headers.common['Authorization'];
|
|
throw new Error('Session expired. Please login again.');
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// Add global refresh cart callback
|
|
let globalRefreshCartCallback = null;
|
|
|
|
export const setGlobalRefreshCart = (callback) => {
|
|
globalRefreshCartCallback = callback;
|
|
};
|
|
|
|
// Update cart functions to use the callback
|
|
export const addToCart = async (productId, quantity = 1) => {
|
|
try {
|
|
const response = await api.post('/cart/add_item/', {
|
|
product_id: productId,
|
|
quantity: quantity
|
|
});
|
|
if (globalRefreshCartCallback) {
|
|
globalRefreshCartCallback();
|
|
}
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error('Error adding to cart:', error);
|
|
if (error.response?.status === 401) {
|
|
await AsyncStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN);
|
|
delete api.defaults.headers.common['Authorization'];
|
|
throw new Error('Session expired. Please login again.');
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const removeFromCart = async (cartItemId) => {
|
|
try {
|
|
// Ensure we have a valid token and set it in headers
|
|
const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
|
|
if (!token) {
|
|
// If no token, fall back to local storage cart handling
|
|
console.log('No auth token found, using local storage for cart');
|
|
const cartData = await AsyncStorage.getItem(STORAGE_KEYS.CART);
|
|
let cart = cartData ? JSON.parse(cartData) : { items: [] };
|
|
|
|
// Filter out the item
|
|
cart.items = cart.items.filter(item => item.id !== cartItemId);
|
|
await AsyncStorage.setItem(STORAGE_KEYS.CART, JSON.stringify(cart));
|
|
return cart;
|
|
}
|
|
|
|
// First, get the cart to find the correct product_id
|
|
const cartResponse = await api.get('/cart/items/');
|
|
const cartItems = cartResponse.data.items || [];
|
|
|
|
// Find the cart item that matches the ID we want to delete
|
|
const cartItem = cartItems.find(item => item.id === cartItemId);
|
|
|
|
if (!cartItem) {
|
|
throw new Error('Cart item not found');
|
|
}
|
|
|
|
// Use the product_id from the cart item
|
|
const productId = cartItem.product_id || cartItem.product;
|
|
console.log(`Found product_id: ${productId} for cart item: ${cartItemId}`);
|
|
|
|
// Use the correct endpoint and method with the product_id
|
|
const response = await api.post('/cart/remove_item/', {
|
|
product_id: productId
|
|
});
|
|
|
|
console.log('Item removed successfully');
|
|
if (globalRefreshCartCallback) globalRefreshCartCallback();
|
|
return response.data;
|
|
} catch (error) {
|
|
|
|
if (error.response?.status === 401) {
|
|
await AsyncStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN);
|
|
delete api.defaults.headers.common['Authorization'];
|
|
throw new Error('Session expired. Please login again.');
|
|
}
|
|
|
|
if (error.response?.status === 403) {
|
|
throw new Error('You do not have permission to remove this item from cart.');
|
|
}
|
|
|
|
if (error.response?.status === 404) {
|
|
throw new Error('Cart item not found');
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// Make deleteCartItem directly handle the delete functionality rather than just being an alias
|
|
export const deleteCartItem = async (itemId) => {
|
|
try {
|
|
return await removeFromCart(itemId);
|
|
} catch (error) {
|
|
console.error('Error in deleteCartItem:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const updateCartItemQuantity = async (cartItemId, quantity) => {
|
|
try {
|
|
// Get the auth token
|
|
const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
|
|
if (!token) {
|
|
throw new Error('Please login to update cart');
|
|
}
|
|
|
|
// Set the auth token in headers
|
|
api.defaults.headers.common['Authorization'] = `Token ${token}`;
|
|
|
|
// Make the API call to update quantity
|
|
const response = await api.post('/cart/update_quantity/', {
|
|
product_id: cartItemId,
|
|
quantity: quantity
|
|
});
|
|
|
|
return response.data;
|
|
} catch (error) {
|
|
if (error.response?.status === 401) {
|
|
// Clear auth data on 401
|
|
await AsyncStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN);
|
|
delete api.defaults.headers.common['Authorization'];
|
|
throw new Error('Session expired. Please login again.');
|
|
}
|
|
if (error.response?.status === 403) {
|
|
throw new Error('You do not have permission to update this cart item.');
|
|
}
|
|
if (error.response?.status === 404) {
|
|
throw new Error('Cart item not found');
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const checkoutCart = async (shippingDetails) => {
|
|
try {
|
|
const { data } = await api.post('/cart/checkout/', shippingDetails);
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error during checkout:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// Product functions
|
|
export const fetchProducts = async (searchQuery = '') => {
|
|
try {
|
|
const { data } = await api.get('/products/', {
|
|
params: {
|
|
search: searchQuery
|
|
}
|
|
});
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error fetching products:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const fetchProductById = async (id) => {
|
|
try {
|
|
// First try the products endpoint
|
|
try {
|
|
const { data } = await api.get(`/products/${id}/`);
|
|
return {
|
|
...data,
|
|
image: data.image ? await processImageUrl(data.image) : null
|
|
};
|
|
} catch (productsError) {
|
|
// If products endpoint fails, try the posts endpoint
|
|
if (productsError.response?.status === 404) {
|
|
const { data } = await api.get(`/posts/${id}/`);
|
|
return {
|
|
...data,
|
|
image: data.image ? await processImageUrl(data.image) : null
|
|
};
|
|
}
|
|
throw productsError;
|
|
}
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// Category functions
|
|
export const fetchCategories = async () => {
|
|
try {
|
|
const { data } = await api.get('/categories/');
|
|
return data;
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const fetchCategoryById = async (id) => {
|
|
try {
|
|
const { data } = await api.get(`/categories/${id}/`);
|
|
return data;
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const fetchProductsByCategory = async (categoryId) => {
|
|
try {
|
|
// Validate categoryId
|
|
if (!categoryId || categoryId === 'undefined' || isNaN(parseInt(categoryId))) {
|
|
throw new Error('Invalid category ID');
|
|
}
|
|
|
|
const { data } = await api.get(`/categories/${categoryId}/products/`);
|
|
|
|
// Validate response data
|
|
if (!data) {
|
|
throw new Error('No data received from server');
|
|
}
|
|
|
|
if (!Array.isArray(data)) {
|
|
throw new Error('Invalid response format from server');
|
|
}
|
|
|
|
return data;
|
|
} catch (error) {
|
|
if (error.response) {
|
|
// The request was made and the server responded with a status code
|
|
// that falls out of the range of 2xx
|
|
throw {
|
|
response: {
|
|
data: error.response.data,
|
|
status: error.response.status
|
|
}
|
|
};
|
|
} else if (error.request) {
|
|
// The request was made but no response was received
|
|
throw new Error('No response from server');
|
|
} else {
|
|
// Something happened in setting up the request that triggered an Error
|
|
throw error;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Order functions
|
|
export const getOrders = async () => {
|
|
try {
|
|
const { data } = await api.get('/orders/');
|
|
return data;
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const getOrderById = async (orderId) => {
|
|
try {
|
|
const { data } = await api.get(`/orders/${orderId}/`);
|
|
return data;
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const placeOrder = async (orderData) => {
|
|
try {
|
|
const { data } = await api.post('/orders/', orderData);
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error placing order:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// New order status management functions
|
|
export const updateOrderStatus = async (orderId, status) => {
|
|
try {
|
|
const { data } = await api.put(`/orders/${orderId}/`, { status });
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error updating order status:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const shipOrder = async (orderId) => {
|
|
try {
|
|
const { data } = await api.post(`/orders/${orderId}/ship/`);
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error shipping order:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const deliverOrder = async (orderId) => {
|
|
try {
|
|
const { data } = await api.post(`/orders/${orderId}/deliver/`);
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error delivering order:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const cancelOrder = async (orderId) => {
|
|
try {
|
|
const { data } = await api.post(`/orders/${orderId}/cancel/`);
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error cancelling order:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// Favorite functions
|
|
export const getFavorites = async () => {
|
|
try {
|
|
const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
|
|
if (!token) {
|
|
// If not logged in, get from local storage
|
|
const favoritesData = await AsyncStorage.getItem(STORAGE_KEYS.FAVORITES);
|
|
return favoritesData ? JSON.parse(favoritesData) : [];
|
|
}
|
|
|
|
const { data } = await api.get('/favorites/');
|
|
return Array.isArray(data) ? data : [];
|
|
} catch (error) {
|
|
return [];
|
|
}
|
|
};
|
|
|
|
export const toggleFavorite = async (product) => {
|
|
try {
|
|
const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
|
|
if (!token) {
|
|
// If not logged in, use local storage
|
|
const favoritesData = await AsyncStorage.getItem(STORAGE_KEYS.FAVORITES);
|
|
const favorites = favoritesData ? JSON.parse(favoritesData) : [];
|
|
|
|
const index = favorites.findIndex(fav => fav.id === product.id);
|
|
if (index === -1) {
|
|
favorites.push(product);
|
|
} else {
|
|
favorites.splice(index, 1);
|
|
}
|
|
|
|
await AsyncStorage.setItem(STORAGE_KEYS.FAVORITES, JSON.stringify(favorites));
|
|
return favorites;
|
|
}
|
|
|
|
// If logged in, use API
|
|
const response = await api.post('/favorites/toggle/', {
|
|
product_id: product.id
|
|
});
|
|
console.log('API toggleFavorite response:', response.data);
|
|
|
|
// The response should contain the updated favorites list
|
|
return Array.isArray(response.data) ? response.data : [];
|
|
} catch (error) {
|
|
console.error('Error toggling favorite:', error);
|
|
return [];
|
|
}
|
|
};
|
|
|
|
export const isProductFavorited = async (productId) => {
|
|
try {
|
|
const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
|
|
if (!token) {
|
|
// If not logged in, check local storage
|
|
const favoritesData = await AsyncStorage.getItem(STORAGE_KEYS.FAVORITES);
|
|
const favorites = favoritesData ? JSON.parse(favoritesData) : [];
|
|
return favorites.some(fav => fav.id === productId);
|
|
}
|
|
|
|
// If logged in, check API
|
|
const { data } = await api.get(`/favorites/check/${productId}/`);
|
|
return data.is_favorited;
|
|
} catch (error) {
|
|
console.error('Error checking favorite status:', error);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
// Rating functions
|
|
export const rateProduct = async (productId, rating, comment = '') => {
|
|
try {
|
|
const { data } = await api.post(`/products/${productId}/rate/`, {
|
|
rating,
|
|
comment
|
|
});
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error rating product:', error);
|
|
if (error.response?.status === 401) {
|
|
await AsyncStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN);
|
|
delete api.defaults.headers.common['Authorization'];
|
|
throw new Error('Session expired. Please login again.');
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// Utility functions
|
|
export const processImageUrl = async (url) => {
|
|
if (!url) {
|
|
console.log('No image URL provided, using local fallback');
|
|
return null; // Return null to indicate no image
|
|
}
|
|
|
|
try {
|
|
// If the URL is already absolute, use it as is
|
|
if (url.startsWith('http')) {
|
|
console.log('Using absolute URL:', url);
|
|
return url;
|
|
}
|
|
|
|
// Remove any leading / or media/ from url
|
|
const cleanedUrl = url
|
|
.replace(/^\/?media\/?/, '') // remove leading "media/" if exists
|
|
.replace(/^\/+/, ''); // remove any other leading slashes
|
|
|
|
// Construct the URL without double-prefixing
|
|
const finalUrl = `${MEDIA_BASE_URL}/media/${cleanedUrl}`;
|
|
console.log('Processed image URL:', finalUrl);
|
|
return finalUrl;
|
|
} catch (error) {
|
|
console.error('Error processing image URL:', error);
|
|
console.error('Original URL:', url);
|
|
return null; // Return null to indicate error
|
|
}
|
|
};
|
|
|
|
export const updatePassword = async (currentPassword, newPassword) => {
|
|
try {
|
|
const response = await api.post('/users/update_password/', {
|
|
current_password: currentPassword,
|
|
new_password: newPassword
|
|
});
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error('Error updating password:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const deleteAccount = async (password) => {
|
|
try {
|
|
const response = await api.post('/users/delete_account/', {
|
|
password: password
|
|
});
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error('Error deleting account:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// Recent searches functions
|
|
export const getUserRecentSearches = async () => {
|
|
try {
|
|
// Always use local storage for recent searches since the API endpoint doesn't exist
|
|
const recentSearchesData = await AsyncStorage.getItem(STORAGE_KEYS.RECENT_SEARCHES);
|
|
return recentSearchesData ? JSON.parse(recentSearchesData) : [];
|
|
} catch (error) {
|
|
console.error('Error getting recent searches:', error);
|
|
return [];
|
|
}
|
|
};
|
|
|
|
export const saveUserRecentSearch = async (searchTerm) => {
|
|
try {
|
|
if (!searchTerm || typeof searchTerm !== 'string') {
|
|
console.warn('Invalid search term provided to saveUserRecentSearch');
|
|
return [];
|
|
}
|
|
|
|
// Always use local storage for recent searches
|
|
const recentSearchesData = await AsyncStorage.getItem(STORAGE_KEYS.RECENT_SEARCHES);
|
|
const recentSearches = recentSearchesData ? JSON.parse(recentSearchesData) : [];
|
|
|
|
// Remove if already exists (to move it to the front)
|
|
const filteredSearches = recentSearches.filter(term => term !== searchTerm);
|
|
|
|
// Add to beginning and limit to 5 recent searches
|
|
const updatedSearches = [searchTerm, ...filteredSearches].slice(0, 5);
|
|
|
|
await AsyncStorage.setItem(STORAGE_KEYS.RECENT_SEARCHES, JSON.stringify(updatedSearches));
|
|
return updatedSearches;
|
|
} catch (error) {
|
|
console.error('Error saving recent search:', error);
|
|
return [];
|
|
}
|
|
};
|
|
|
|
export const removeUserRecentSearch = async (searchTerm) => {
|
|
try {
|
|
if (!searchTerm || typeof searchTerm !== 'string') {
|
|
console.warn('Invalid search term provided to removeUserRecentSearch');
|
|
return [];
|
|
}
|
|
|
|
// Always use local storage for recent searches
|
|
const recentSearchesData = await AsyncStorage.getItem(STORAGE_KEYS.RECENT_SEARCHES);
|
|
const recentSearches = recentSearchesData ? JSON.parse(recentSearchesData) : [];
|
|
|
|
const updatedSearches = recentSearches.filter(term => term !== searchTerm);
|
|
await AsyncStorage.setItem(STORAGE_KEYS.RECENT_SEARCHES, JSON.stringify(updatedSearches));
|
|
return updatedSearches;
|
|
} catch (error) {
|
|
console.error('Error removing recent search:', error);
|
|
return [];
|
|
}
|
|
};
|
|
|
|
export const clearAllUserRecentSearches = async () => {
|
|
try {
|
|
// Always use local storage for recent searches
|
|
await AsyncStorage.removeItem(STORAGE_KEYS.RECENT_SEARCHES);
|
|
return [];
|
|
} catch (error) {
|
|
console.error('Error clearing recent searches:', error);
|
|
return [];
|
|
}
|
|
};
|
|
|
|
export const getPrivacyPolicy = async () => {
|
|
try {
|
|
const response = await api.get('/privacy-policies/');
|
|
return response.data; // Return all policies
|
|
} catch (error) {
|
|
console.error('Error fetching privacy policy:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const getFAQs = async (category = 'All') => {
|
|
try {
|
|
const { data } = await api.get('/faqs/', {
|
|
params: {
|
|
category: category
|
|
}
|
|
});
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error fetching FAQs:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const getFAQCategories = async () => {
|
|
try {
|
|
const { data } = await api.get('/faqs/categories/');
|
|
return data.map(category => category.value);
|
|
} catch (error) {
|
|
console.error('Error fetching FAQ categories:', error);
|
|
// Return default categories if API fails
|
|
return ['All', 'Services', 'General', 'Account'];
|
|
}
|
|
};
|
|
|
|
export const getContacts = async () => {
|
|
try {
|
|
const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
|
|
if (!token) {
|
|
// Return default contacts if not logged in
|
|
return [
|
|
{
|
|
id: 1,
|
|
contact_type: 'customer_service',
|
|
title: 'Customer Service',
|
|
value: '(480)555-0103',
|
|
is_active: true
|
|
},
|
|
{
|
|
id: 2,
|
|
contact_type: 'whatsapp',
|
|
title: 'WhatsApp',
|
|
value: '(480)555-0103',
|
|
is_active: true
|
|
},
|
|
{
|
|
id: 3,
|
|
contact_type: 'website',
|
|
title: 'Website',
|
|
value: 'www.example.com',
|
|
is_active: true
|
|
},
|
|
{
|
|
id: 4,
|
|
contact_type: 'facebook',
|
|
title: 'Facebook',
|
|
value: '@example',
|
|
is_active: true
|
|
},
|
|
{
|
|
id: 5,
|
|
contact_type: 'instagram',
|
|
title: 'Instagram',
|
|
value: '@example',
|
|
is_active: true
|
|
},
|
|
{
|
|
id: 6,
|
|
contact_type: 'twitter',
|
|
title: 'Twitter',
|
|
value: '@example',
|
|
is_active: true
|
|
}
|
|
];
|
|
}
|
|
|
|
// Set the auth token in headers
|
|
api.defaults.headers.common['Authorization'] = `Token ${token}`;
|
|
|
|
const { data } = await api.get('/contacts/');
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error fetching contacts:', error);
|
|
// Return default contacts on error
|
|
return [
|
|
{
|
|
id: 1,
|
|
contact_type: 'customer_service',
|
|
title: 'Customer Service',
|
|
value: '(480)555-0103',
|
|
is_active: true
|
|
},
|
|
{
|
|
id: 2,
|
|
contact_type: 'whatsapp',
|
|
title: 'WhatsApp',
|
|
value: '(480)555-0103',
|
|
is_active: true
|
|
},
|
|
{
|
|
id: 3,
|
|
contact_type: 'website',
|
|
title: 'Website',
|
|
value: 'www.example.com',
|
|
is_active: true
|
|
},
|
|
{
|
|
id: 4,
|
|
contact_type: 'facebook',
|
|
title: 'Facebook',
|
|
value: '@example',
|
|
is_active: true
|
|
},
|
|
{
|
|
id: 5,
|
|
contact_type: 'instagram',
|
|
title: 'Instagram',
|
|
value: '@example',
|
|
is_active: true
|
|
},
|
|
{
|
|
id: 6,
|
|
contact_type: 'twitter',
|
|
title: 'Twitter',
|
|
value: '@example',
|
|
is_active: true
|
|
}
|
|
];
|
|
}
|
|
};
|
|
|
|
export const getInviteMessage = async () => {
|
|
return {
|
|
message: "Hey! I've been shopping on Vendora App and it's been amazing! They have awesome deals, fast delivery, and quality products. Use my link to sign up and get a special discount on your first order!",
|
|
link: "https://play.google.com/store/apps/details?id=com.vendora.app"
|
|
};
|
|
};
|
|
|
|
export const fetchSliders = async () => {
|
|
try {
|
|
const { data } = await api.get('/sliders/');
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error fetching sliders:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// Coupon functions
|
|
export const getCoupons = async () => {
|
|
try {
|
|
const { data } = await api.get('/coupons/');
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error fetching coupons:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const validateCoupon = async (code, cartTotal) => {
|
|
try {
|
|
const { data } = await api.post('/validate-coupon/', {
|
|
code,
|
|
cart_total: cartTotal
|
|
});
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error validating coupon:', error);
|
|
if (error.response?.data) {
|
|
// Return the error response data with a valid property set to false
|
|
return {
|
|
valid: false,
|
|
message: error.response.data.message || 'Invalid coupon code',
|
|
...error.response.data
|
|
};
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const getNotifications = async () => {
|
|
try {
|
|
const { data } = await api.get('/notifications/');
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error fetching notifications:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const markNotificationAsRead = async (notificationId) => {
|
|
try {
|
|
const { data } = await api.put(`/notifications/${notificationId}/read/`);
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error marking notification as read:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const deleteNotification = async (notificationId) => {
|
|
try {
|
|
const { data } = await api.delete(`/notifications/${notificationId}/`);
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error deleting notification:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const clearAllNotifications = async () => {
|
|
try {
|
|
const { data } = await api.delete('/notifications/clear_all/');
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error clearing notifications:', error);
|
|
throw error;
|
|
}
|
|
};
|