cart
This commit is contained in:
195
services/api.ts
195
services/api.ts
@@ -0,0 +1,195 @@
|
||||
import { API_CONFIG } from '../constants/Config';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
const API_URL = API_CONFIG.BASE_URL;
|
||||
|
||||
interface RequestOptions extends RequestInit {
|
||||
requireAuth?: boolean;
|
||||
}
|
||||
|
||||
class ApiClient {
|
||||
private baseURL: string;
|
||||
|
||||
constructor(baseURL: string) {
|
||||
this.baseURL = baseURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy token từ AsyncStorage
|
||||
*/
|
||||
private async getAuthToken(): Promise<string | null> {
|
||||
try {
|
||||
return await AsyncStorage.getItem('authToken');
|
||||
} catch (error) {
|
||||
console.error('Error getting auth token:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tạo headers cho request
|
||||
*/
|
||||
private async createHeaders(requireAuth: boolean = false): Promise<HeadersInit> {
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
};
|
||||
|
||||
if (requireAuth) {
|
||||
const token = await this.getAuthToken();
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Xử lý response
|
||||
*/
|
||||
private async handleResponse<T>(response: Response): Promise<T> {
|
||||
if (!response.ok) {
|
||||
let errorMessage = `Request failed: ${response.status} ${response.statusText}`;
|
||||
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
errorMessage = errorData.message || errorData.error || errorMessage;
|
||||
} catch {
|
||||
const text = await response.text().catch(() => '');
|
||||
errorMessage = text || errorMessage;
|
||||
}
|
||||
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
// Xử lý response 204 No Content
|
||||
if (response.status === 204) {
|
||||
return {} as T;
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* GET request
|
||||
*/
|
||||
async get<T>(endpoint: string, options: RequestOptions = {}): Promise<T> {
|
||||
try {
|
||||
const { requireAuth = false, ...fetchOptions } = options;
|
||||
const headers = await this.createHeaders(requireAuth);
|
||||
|
||||
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
...fetchOptions,
|
||||
});
|
||||
|
||||
return await this.handleResponse<T>(response);
|
||||
} catch (error) {
|
||||
if (error instanceof TypeError && error.message.includes('fetch')) {
|
||||
throw new Error('Không thể kết nối đến server. Vui lòng kiểm tra kết nối mạng.');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST request
|
||||
*/
|
||||
async post<T>(endpoint: string, data?: any, options: RequestOptions = {}): Promise<T> {
|
||||
try {
|
||||
const { requireAuth = false, ...fetchOptions } = options;
|
||||
const headers = await this.createHeaders(requireAuth);
|
||||
|
||||
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: data ? JSON.stringify(data) : undefined,
|
||||
...fetchOptions,
|
||||
});
|
||||
|
||||
return await this.handleResponse<T>(response);
|
||||
} catch (error) {
|
||||
if (error instanceof TypeError && error.message.includes('fetch')) {
|
||||
throw new Error('Không thể kết nối đến server. Vui lòng kiểm tra kết nối mạng.');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT request
|
||||
*/
|
||||
async put<T>(endpoint: string, data?: any, options: RequestOptions = {}): Promise<T> {
|
||||
try {
|
||||
const { requireAuth = false, ...fetchOptions } = options;
|
||||
const headers = await this.createHeaders(requireAuth);
|
||||
|
||||
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: data ? JSON.stringify(data) : undefined,
|
||||
...fetchOptions,
|
||||
});
|
||||
|
||||
return await this.handleResponse<T>(response);
|
||||
} catch (error) {
|
||||
if (error instanceof TypeError && error.message.includes('fetch')) {
|
||||
throw new Error('Không thể kết nối đến server. Vui lòng kiểm tra kết nối mạng.');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE request
|
||||
*/
|
||||
async delete<T>(endpoint: string, options: RequestOptions = {}): Promise<T> {
|
||||
try {
|
||||
const { requireAuth = false, ...fetchOptions } = options;
|
||||
const headers = await this.createHeaders(requireAuth);
|
||||
|
||||
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
||||
method: 'DELETE',
|
||||
headers,
|
||||
...fetchOptions,
|
||||
});
|
||||
|
||||
return await this.handleResponse<T>(response);
|
||||
} catch (error) {
|
||||
if (error instanceof TypeError && error.message.includes('fetch')) {
|
||||
throw new Error('Không thể kết nối đến server. Vui lòng kiểm tra kết nối mạng.');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH request
|
||||
*/
|
||||
async patch<T>(endpoint: string, data?: any, options: RequestOptions = {}): Promise<T> {
|
||||
try {
|
||||
const { requireAuth = false, ...fetchOptions } = options;
|
||||
const headers = await this.createHeaders(requireAuth);
|
||||
|
||||
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
||||
method: 'PATCH',
|
||||
headers,
|
||||
body: data ? JSON.stringify(data) : undefined,
|
||||
...fetchOptions,
|
||||
});
|
||||
|
||||
return await this.handleResponse<T>(response);
|
||||
} catch (error) {
|
||||
if (error instanceof TypeError && error.message.includes('fetch')) {
|
||||
throw new Error('Không thể kết nối đến server. Vui lòng kiểm tra kết nối mạng.');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
const api = new ApiClient(API_URL);
|
||||
export default api;
|
||||
375
services/auth.ts
375
services/auth.ts
@@ -1,34 +1,379 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
const API_URL = "http://192.168.2.141:8080/api/auth";
|
||||
|
||||
export async function register(email: string, password: string) {
|
||||
export interface AuthResponse {
|
||||
token: string;
|
||||
userId: number;
|
||||
email: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode base64Url string
|
||||
* React Native compatible - không dùng atob
|
||||
*/
|
||||
function base64UrlDecode(str: string): string {
|
||||
try {
|
||||
// Replace URL-safe characters
|
||||
let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
|
||||
|
||||
// Add padding
|
||||
while (base64.length % 4) {
|
||||
base64 += '=';
|
||||
}
|
||||
|
||||
// Try to use atob if available (browser environment)
|
||||
if (typeof atob !== 'undefined') {
|
||||
return atob(base64);
|
||||
}
|
||||
|
||||
// For React Native - use Buffer or manual decode
|
||||
// React Native có thể có Buffer từ polyfills
|
||||
if (typeof Buffer !== 'undefined') {
|
||||
return Buffer.from(base64, 'base64').toString('utf-8');
|
||||
}
|
||||
|
||||
// Fallback: manual base64 decode (simple implementation)
|
||||
// Chỉ hoạt động với ASCII characters
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
let result = '';
|
||||
let i = 0;
|
||||
|
||||
str = str.replace(/[^A-Za-z0-9\+\/]/g, '');
|
||||
|
||||
while (i < str.length) {
|
||||
const encoded1 = chars.indexOf(str.charAt(i++));
|
||||
const encoded2 = chars.indexOf(str.charAt(i++));
|
||||
const encoded3 = chars.indexOf(str.charAt(i++));
|
||||
const encoded4 = chars.indexOf(str.charAt(i++));
|
||||
|
||||
const bitmap = (encoded1 << 18) | (encoded2 << 12) | (encoded3 << 6) | encoded4;
|
||||
|
||||
result += String.fromCharCode((bitmap >> 16) & 255);
|
||||
if (encoded3 !== 64) result += String.fromCharCode((bitmap >> 8) & 255);
|
||||
if (encoded4 !== 64) result += String.fromCharCode(bitmap & 255);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error decoding base64:', error);
|
||||
throw new Error('Không thể decode base64: ' + (error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode JWT token và lấy payload
|
||||
*/
|
||||
function decodeJWT(token: string): any {
|
||||
try {
|
||||
const parts = token.split('.');
|
||||
if (parts.length !== 3) {
|
||||
throw new Error('Token không đúng format JWT (phải có 3 phần)');
|
||||
}
|
||||
|
||||
const payload = parts[1];
|
||||
const decoded = base64UrlDecode(payload);
|
||||
return JSON.parse(decoded);
|
||||
} catch (error) {
|
||||
console.error('Error decoding JWT:', error);
|
||||
throw new Error('Không thể decode JWT token: ' + (error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function register(email: string, password: string): Promise<AuthResponse> {
|
||||
const response = await fetch(`${API_URL}/register`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error("Đăng ký thất bại");
|
||||
return await response.json();
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: "Đăng ký thất bại" }));
|
||||
throw new Error(error.message || "Đăng ký thất bại");
|
||||
}
|
||||
|
||||
const rawData = await response.json();
|
||||
console.log('Register API raw response:', JSON.stringify(rawData, null, 2));
|
||||
|
||||
// Kiểm tra token
|
||||
if (!rawData.token) {
|
||||
throw new Error('Response không có token');
|
||||
}
|
||||
|
||||
// Lấy userId từ response (server trả về kiểu Long)
|
||||
let userId: number;
|
||||
let userEmail: string = rawData.email || email;
|
||||
|
||||
// Lấy userId từ response - ưu tiên userId (camelCase) trước
|
||||
if (rawData.userId !== undefined && rawData.userId !== null) {
|
||||
userId = typeof rawData.userId === 'string' ? parseInt(rawData.userId, 10) : rawData.userId;
|
||||
} else if (rawData.user_id !== undefined && rawData.user_id !== null) {
|
||||
userId = typeof rawData.user_id === 'string' ? parseInt(rawData.user_id, 10) : rawData.user_id;
|
||||
} else if (rawData.id !== undefined && rawData.id !== null) {
|
||||
userId = typeof rawData.id === 'string' ? parseInt(rawData.id, 10) : rawData.id;
|
||||
} else {
|
||||
// Fallback: thử decode JWT token nếu không có trong response
|
||||
console.warn('UserId không có trong response, thử decode JWT token...');
|
||||
try {
|
||||
const payload = decodeJWT(rawData.token);
|
||||
console.log('JWT payload decoded (register):', payload);
|
||||
|
||||
userId = payload.userId || payload.user_id || payload.id || payload.sub;
|
||||
if (typeof userId === 'string') {
|
||||
userId = parseInt(userId, 10);
|
||||
}
|
||||
|
||||
if (!userId && userId !== 0) {
|
||||
throw new Error('Không tìm thấy userId trong JWT token');
|
||||
}
|
||||
|
||||
if (payload.email) {
|
||||
userEmail = payload.email;
|
||||
}
|
||||
|
||||
console.log('Extracted userId from JWT token (register):', userId);
|
||||
} catch (error: any) {
|
||||
console.error('Error decoding JWT token (register):', error);
|
||||
throw new Error(
|
||||
'Không thể lấy userId từ response. ' +
|
||||
'Response fields: ' + Object.keys(rawData).join(', ') + '. ' +
|
||||
'Vui lòng đảm bảo server trả về userId trong AuthResponse.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate userId
|
||||
if (userId === undefined || userId === null) {
|
||||
throw new Error('UserId không tồn tại trong response');
|
||||
}
|
||||
if (isNaN(userId)) {
|
||||
throw new Error('UserId không hợp lệ (phải là số): ' + userId);
|
||||
}
|
||||
|
||||
console.log('Got userId from response (register):', userId, '(type: number)');
|
||||
|
||||
// Tạo AuthResponse với userId
|
||||
const data: AuthResponse = {
|
||||
token: rawData.token,
|
||||
userId: userId,
|
||||
email: userEmail,
|
||||
};
|
||||
|
||||
console.log('Final register data:', data);
|
||||
|
||||
// Validate data
|
||||
if (!data.token) {
|
||||
throw new Error('Token không tồn tại trong response');
|
||||
}
|
||||
if (data.userId === undefined || data.userId === null) {
|
||||
throw new Error('UserId không tồn tại');
|
||||
}
|
||||
|
||||
await saveAuthData(data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function login(email: string, password: string) {
|
||||
export async function login(email: string, password: string): Promise<AuthResponse> {
|
||||
const response = await fetch(`${API_URL}/login`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error("Đăng nhập thất bại");
|
||||
return await response.json();
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: "Đăng nhập thất bại" }));
|
||||
throw new Error(error.message || "Đăng nhập thất bại");
|
||||
}
|
||||
|
||||
const rawData = await response.json();
|
||||
console.log('Login API raw response:', JSON.stringify(rawData, null, 2));
|
||||
|
||||
// Kiểm tra token
|
||||
if (!rawData.token) {
|
||||
throw new Error('Response không có token');
|
||||
}
|
||||
|
||||
// Lấy userId từ response (server trả về kiểu Long)
|
||||
let userId: number;
|
||||
let userEmail: string = rawData.email || email;
|
||||
|
||||
// Lấy userId từ response - ưu tiên userId (camelCase) trước
|
||||
if (rawData.userId !== undefined && rawData.userId !== null) {
|
||||
userId = typeof rawData.userId === 'string' ? parseInt(rawData.userId, 10) : rawData.userId;
|
||||
} else if (rawData.user_id !== undefined && rawData.user_id !== null) {
|
||||
userId = typeof rawData.user_id === 'string' ? parseInt(rawData.user_id, 10) : rawData.user_id;
|
||||
} else if (rawData.id !== undefined && rawData.id !== null) {
|
||||
userId = typeof rawData.id === 'string' ? parseInt(rawData.id, 10) : rawData.id;
|
||||
} else {
|
||||
// Fallback: thử decode JWT token nếu không có trong response
|
||||
console.warn('UserId không có trong response, thử decode JWT token...');
|
||||
try {
|
||||
const payload = decodeJWT(rawData.token);
|
||||
console.log('JWT payload decoded:', payload);
|
||||
|
||||
userId = payload.userId || payload.user_id || payload.id || payload.sub;
|
||||
if (typeof userId === 'string') {
|
||||
userId = parseInt(userId, 10);
|
||||
}
|
||||
|
||||
if (!userId && userId !== 0) {
|
||||
throw new Error('Không tìm thấy userId trong JWT token');
|
||||
}
|
||||
|
||||
if (payload.email) {
|
||||
userEmail = payload.email;
|
||||
}
|
||||
|
||||
console.log('Extracted userId from JWT token:', userId);
|
||||
} catch (error: any) {
|
||||
console.error('Error decoding JWT token:', error);
|
||||
throw new Error(
|
||||
'Không thể lấy userId từ response. ' +
|
||||
'Response fields: ' + Object.keys(rawData).join(', ') + '. ' +
|
||||
'Vui lòng đảm bảo server trả về userId trong AuthResponse.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate userId
|
||||
if (userId === undefined || userId === null) {
|
||||
throw new Error('UserId không tồn tại trong response');
|
||||
}
|
||||
if (isNaN(userId)) {
|
||||
throw new Error('UserId không hợp lệ (phải là số): ' + userId);
|
||||
}
|
||||
|
||||
console.log('Got userId from response:', userId, '(type: number)');
|
||||
|
||||
// Tạo AuthResponse với userId
|
||||
const data: AuthResponse = {
|
||||
token: rawData.token,
|
||||
userId: userId,
|
||||
email: userEmail,
|
||||
};
|
||||
|
||||
console.log('Final auth data:', data);
|
||||
|
||||
// Validate data
|
||||
if (!data.token) {
|
||||
throw new Error('Token không tồn tại trong response');
|
||||
}
|
||||
if (data.userId === undefined || data.userId === null) {
|
||||
throw new Error('UserId không tồn tại');
|
||||
}
|
||||
|
||||
// Lưu token và user info vào AsyncStorage
|
||||
await saveAuthData(data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function logout(token: string) {
|
||||
const response = await fetch(`${API_URL}/logout`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
return response.ok;
|
||||
export async function logout(token: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/logout`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
// Xóa token và user info khỏi AsyncStorage
|
||||
await clearAuthData();
|
||||
|
||||
return response.ok;
|
||||
} catch (error) {
|
||||
// Vẫn xóa dữ liệu local ngay cả khi API call thất bại
|
||||
await clearAuthData();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lưu thông tin auth vào AsyncStorage
|
||||
*/
|
||||
export async function saveAuthData(data: AuthResponse): Promise<void> {
|
||||
try {
|
||||
const userData = {
|
||||
userId: data.userId,
|
||||
email: data.email,
|
||||
};
|
||||
|
||||
console.log('Saving auth data:', {
|
||||
hasToken: !!data.token,
|
||||
userId: data.userId,
|
||||
email: data.email
|
||||
});
|
||||
|
||||
await AsyncStorage.multiSet([
|
||||
['authToken', data.token],
|
||||
['user', JSON.stringify(userData)],
|
||||
]);
|
||||
|
||||
// Verify data was saved
|
||||
const savedUser = await AsyncStorage.getItem('user');
|
||||
const savedToken = await AsyncStorage.getItem('authToken');
|
||||
console.log('Auth data saved - User:', savedUser, 'Token exists:', !!savedToken);
|
||||
|
||||
if (!savedUser || !savedToken) {
|
||||
throw new Error('Failed to save auth data');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving auth data:', error);
|
||||
throw new Error('Không thể lưu thông tin đăng nhập');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Xóa thông tin auth khỏi AsyncStorage
|
||||
*/
|
||||
export async function clearAuthData(): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.multiRemove(['authToken', 'user']);
|
||||
} catch (error) {
|
||||
console.error('Error clearing auth data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy thông tin user từ AsyncStorage
|
||||
*/
|
||||
export async function getUser(): Promise<{ userId: number; email: string } | null> {
|
||||
try {
|
||||
const userStr = await AsyncStorage.getItem('user');
|
||||
if (userStr) {
|
||||
return JSON.parse(userStr);
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Error getting user:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy auth token từ AsyncStorage
|
||||
*/
|
||||
export async function getAuthToken(): Promise<string | null> {
|
||||
try {
|
||||
return await AsyncStorage.getItem('authToken');
|
||||
} catch (error) {
|
||||
console.error('Error getting auth token:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kiểm tra user đã đăng nhập chưa
|
||||
*/
|
||||
export async function isAuthenticated(): Promise<boolean> {
|
||||
try {
|
||||
const token = await AsyncStorage.getItem('authToken');
|
||||
const user = await AsyncStorage.getItem('user');
|
||||
return !!(token && user);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import api from './api';
|
||||
|
||||
export interface CartItem {
|
||||
cartItemId: number;
|
||||
productId: number;
|
||||
productName: string;
|
||||
productImage: string;
|
||||
quantity: number;
|
||||
price: number;
|
||||
subtotal: number;
|
||||
}
|
||||
|
||||
export interface CartResponse {
|
||||
cartId: number;
|
||||
userId: number;
|
||||
items: CartItem[];
|
||||
totalAmount: number;
|
||||
totalItems: number;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface AddToCartRequest {
|
||||
productId: number;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
export interface UpdateCartItemRequest {
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
const cartService = {
|
||||
// Thêm sản phẩm vào giỏ hàng
|
||||
addToCart: async (userId: number, data: AddToCartRequest): Promise<CartResponse> => {
|
||||
console.log('=== ADD TO CART REQUEST ===', { userId, data });
|
||||
const response = await api.post<CartResponse>(
|
||||
`/cart/${userId}`,
|
||||
data,
|
||||
{ requireAuth: true }
|
||||
);
|
||||
console.log('=== ADD TO CART RESPONSE ===', response);
|
||||
return response;
|
||||
},
|
||||
|
||||
// Lấy danh sách sản phẩm trong giỏ hàng
|
||||
getCart: async (userId: number): Promise<CartResponse> => {
|
||||
console.log('=== GET CART REQUEST ===', { userId });
|
||||
const response = await api.get<CartResponse>(
|
||||
`/cart/${userId}`,
|
||||
{ requireAuth: true }
|
||||
);
|
||||
console.log('=== GET CART RESPONSE ===', response);
|
||||
return response;
|
||||
},
|
||||
|
||||
// Cập nhật số lượng sản phẩm
|
||||
updateCartItem: async (
|
||||
userId: number,
|
||||
cartItemId: number,
|
||||
data: UpdateCartItemRequest
|
||||
): Promise<CartResponse> => {
|
||||
console.log('=== UPDATE CART ITEM REQUEST ===', { userId, cartItemId, data });
|
||||
const response = await api.put<CartResponse>(
|
||||
`/cart/${userId}/items/${cartItemId}`,
|
||||
data,
|
||||
{ requireAuth: true }
|
||||
);
|
||||
console.log('=== UPDATE CART ITEM RESPONSE ===', response);
|
||||
return response;
|
||||
},
|
||||
|
||||
// Xóa một sản phẩm khỏi giỏ hàng
|
||||
removeCartItem: async (userId: number, cartItemId: number): Promise<CartResponse> => {
|
||||
console.log('=== REMOVE CART ITEM REQUEST ===', { userId, cartItemId });
|
||||
const response = await api.delete<CartResponse>(
|
||||
`/cart/${userId}/items/${cartItemId}`,
|
||||
{ requireAuth: true }
|
||||
);
|
||||
console.log('=== REMOVE CART ITEM RESPONSE ===', response);
|
||||
return response;
|
||||
},
|
||||
|
||||
// Xóa tất cả sản phẩm khỏi giỏ hàng
|
||||
clearCart: async (userId: number): Promise<void> => {
|
||||
console.log('=== CLEAR CART REQUEST ===', { userId });
|
||||
await api.delete<void>(
|
||||
`/cart/${userId}/clear`,
|
||||
{ requireAuth: true }
|
||||
);
|
||||
console.log('=== CLEAR CART SUCCESS ===');
|
||||
},
|
||||
};
|
||||
|
||||
export default cartService;
|
||||
@@ -1,6 +1,4 @@
|
||||
import { API_CONFIG } from '../constants/Config';
|
||||
|
||||
const API_URL = API_CONFIG.BASE_URL;
|
||||
import api from './api';
|
||||
|
||||
export interface ProductResponse {
|
||||
productId: number;
|
||||
@@ -34,71 +32,21 @@ class ProductService {
|
||||
sortBy: string = 'productId',
|
||||
sortDir: 'asc' | 'desc' = 'asc'
|
||||
): Promise<ProductListResponse> {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
size: size.toString(),
|
||||
sortBy,
|
||||
sortDir,
|
||||
});
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
size: size.toString(),
|
||||
sortBy,
|
||||
sortDir,
|
||||
});
|
||||
|
||||
const url = `${API_URL}/products?${params}`;
|
||||
console.log('Fetching products from:', url);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
let errorMessage = `Failed to fetch products: ${response.status} ${response.statusText}`;
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
errorMessage = errorData.message || errorData.error || errorMessage;
|
||||
} catch {
|
||||
// If response is not JSON, use the status text
|
||||
const text = await response.text().catch(() => '');
|
||||
errorMessage = text || errorMessage;
|
||||
}
|
||||
console.error('API Error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
url,
|
||||
});
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
if (error instanceof TypeError && error.message.includes('fetch')) {
|
||||
console.error('Network error - API may be unreachable:', error);
|
||||
throw new Error('Không thể kết nối đến server. Vui lòng kiểm tra kết nối mạng.');
|
||||
}
|
||||
console.error('Error fetching products:', error);
|
||||
throw error;
|
||||
}
|
||||
return await api.get<ProductListResponse>(`/products?${params}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy chi tiết sản phẩm theo ID
|
||||
*/
|
||||
async getProductById(id: number): Promise<ProductResponse> {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/products/${id}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Product not found');
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error fetching product:', error);
|
||||
throw error;
|
||||
}
|
||||
return await api.get<ProductResponse>(`/products/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,25 +59,14 @@ class ProductService {
|
||||
sortBy: string = 'productId',
|
||||
sortDir: 'asc' | 'desc' = 'asc'
|
||||
): Promise<ProductListResponse> {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
size: size.toString(),
|
||||
sortBy,
|
||||
sortDir,
|
||||
});
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
size: size.toString(),
|
||||
sortBy,
|
||||
sortDir,
|
||||
});
|
||||
|
||||
const response = await fetch(`${API_URL}/products/category/${categoryId}?${params}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch products by category');
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error fetching products by category:', error);
|
||||
throw error;
|
||||
}
|
||||
return await api.get<ProductListResponse>(`/products/category/${categoryId}?${params}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,26 +79,15 @@ class ProductService {
|
||||
sortBy: string = 'productId',
|
||||
sortDir: 'asc' | 'desc' = 'asc'
|
||||
): Promise<ProductListResponse> {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
keyword,
|
||||
page: page.toString(),
|
||||
size: size.toString(),
|
||||
sortBy,
|
||||
sortDir,
|
||||
});
|
||||
const params = new URLSearchParams({
|
||||
keyword,
|
||||
page: page.toString(),
|
||||
size: size.toString(),
|
||||
sortBy,
|
||||
sortDir,
|
||||
});
|
||||
|
||||
const response = await fetch(`${API_URL}/products/search?${params}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to search products');
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error searching products:', error);
|
||||
throw error;
|
||||
}
|
||||
return await api.get<ProductListResponse>(`/products/search?${params}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,25 +99,14 @@ class ProductService {
|
||||
sortBy: string = 'productId',
|
||||
sortDir: 'asc' | 'desc' = 'asc'
|
||||
): Promise<ProductListResponse> {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
size: size.toString(),
|
||||
sortBy,
|
||||
sortDir,
|
||||
});
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
size: size.toString(),
|
||||
sortBy,
|
||||
sortDir,
|
||||
});
|
||||
|
||||
const response = await fetch(`${API_URL}/products/available?${params}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch available products');
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error fetching available products:', error);
|
||||
throw error;
|
||||
}
|
||||
return await api.get<ProductListResponse>(`/products/available?${params}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user