diff --git a/app/(tabs)/cart.tsx b/app/(tabs)/cart.tsx
new file mode 100644
index 0000000..a28e492
--- /dev/null
+++ b/app/(tabs)/cart.tsx
@@ -0,0 +1,253 @@
+import React from 'react';
+import {
+ View,
+ Text,
+ FlatList,
+ StyleSheet,
+ TouchableOpacity,
+ ActivityIndicator,
+ Alert,
+ RefreshControl,
+} from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { Ionicons } from '@expo/vector-icons';
+import { useFocusEffect } from '@react-navigation/native';
+import { useCart } from '../../hooks/useCart';
+import CartItemCard from '../../components/CartItemCard';
+
+export default function CartScreen() {
+ const {
+ cart,
+ loading,
+ refreshing,
+ refreshCart,
+ updateQuantity,
+ removeItem,
+ clearCart,
+ } = useCart();
+
+ // Tự động refresh cart mỗi khi vào trang
+ useFocusEffect(
+ React.useCallback(() => {
+ console.log('=== CartScreen focused - Refreshing cart ===');
+ refreshCart();
+ }, [refreshCart])
+ );
+
+ const handleClearCart = () => {
+ Alert.alert(
+ 'Xác nhận',
+ 'Bạn có chắc muốn xóa tất cả sản phẩm trong giỏ hàng?',
+ [
+ { text: 'Hủy', style: 'cancel' },
+ {
+ text: 'Xóa tất cả',
+ onPress: clearCart,
+ style: 'destructive'
+ },
+ ]
+ );
+ };
+
+ const handleCheckout = () => {
+ Alert.alert('Thông báo', 'Chức năng thanh toán đang được phát triển');
+ };
+
+ const formatPrice = (price: number) => {
+ return new Intl.NumberFormat('vi-VN', {
+ style: 'currency',
+ currency: 'VND',
+ }).format(price);
+ };
+
+ if (loading && !cart) {
+ return (
+
+
+
+ Đang tải giỏ hàng...
+
+
+ );
+ }
+
+ if (!cart || cart.items.length === 0) {
+ return (
+
+
+ Giỏ hàng
+
+
+
+
+ Giỏ hàng trống
+
+ Hãy thêm sản phẩm vào giỏ hàng để mua sắm
+
+
+
+ );
+ }
+
+ return (
+
+
+ Giỏ hàng
+
+
+ Xóa tất cả
+
+
+
+ item.cartItemId.toString()}
+ renderItem={({ item }) => (
+
+ )}
+ contentContainerStyle={styles.listContent}
+ refreshControl={
+
+ }
+ />
+
+
+
+ Tổng số lượng:
+ {cart.totalItems} sản phẩm
+
+
+
+ Tổng tiền:
+
+ {formatPrice(cart.totalAmount)}
+
+
+
+
+ Thanh toán
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#f5f5f5',
+ },
+ header: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ padding: 16,
+ backgroundColor: '#fff',
+ borderBottomWidth: 1,
+ borderBottomColor: '#e0e0e0',
+ },
+ headerTitle: {
+ fontSize: 24,
+ fontWeight: '700',
+ color: '#333',
+ },
+ clearButton: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ padding: 8,
+ },
+ clearText: {
+ fontSize: 14,
+ color: '#ff4444',
+ marginLeft: 4,
+ fontWeight: '500',
+ },
+ loadingContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ loadingText: {
+ marginTop: 12,
+ fontSize: 16,
+ color: '#666',
+ },
+ emptyContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ padding: 32,
+ },
+ emptyText: {
+ fontSize: 20,
+ fontWeight: '600',
+ color: '#666',
+ marginTop: 16,
+ },
+ emptySubtext: {
+ fontSize: 14,
+ color: '#999',
+ marginTop: 8,
+ textAlign: 'center',
+ },
+ listContent: {
+ padding: 16,
+ },
+ footer: {
+ backgroundColor: '#fff',
+ padding: 16,
+ borderTopWidth: 1,
+ borderTopColor: '#e0e0e0',
+ },
+ summaryRow: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ marginBottom: 12,
+ },
+ summaryLabel: {
+ fontSize: 15,
+ color: '#666',
+ },
+ summaryValue: {
+ fontSize: 15,
+ color: '#333',
+ fontWeight: '500',
+ },
+ totalLabel: {
+ fontSize: 18,
+ fontWeight: '600',
+ color: '#333',
+ },
+ totalValue: {
+ fontSize: 20,
+ fontWeight: '700',
+ color: '#ff6b6b',
+ },
+ checkoutButton: {
+ backgroundColor: '#ff6b6b',
+ flexDirection: 'row',
+ justifyContent: 'center',
+ alignItems: 'center',
+ padding: 16,
+ borderRadius: 12,
+ marginTop: 16,
+ },
+ checkoutText: {
+ fontSize: 17,
+ fontWeight: '700',
+ color: '#fff',
+ marginRight: 8,
+ },
+});
\ No newline at end of file
diff --git a/app/(tabs)/products/[id].tsx b/app/(tabs)/products/[id].tsx
index 01b706f..ae12969 100644
--- a/app/(tabs)/products/[id].tsx
+++ b/app/(tabs)/products/[id].tsx
@@ -1,18 +1,19 @@
-import React from 'react';
+import { Ionicons } from '@expo/vector-icons';
+import { useLocalSearchParams, useRouter } from 'expo-router';
+import React, { useState } from 'react';
import {
- View,
- Text,
- StyleSheet,
- ScrollView,
- Image,
ActivityIndicator,
- TouchableOpacity,
Dimensions,
+ Image,
+ ScrollView,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
-import { useLocalSearchParams, useRouter } from 'expo-router';
+import { useCart } from '../../../hooks/useCart';
import { useProduct } from '../../../hooks/useProducts';
-import { Ionicons } from '@expo/vector-icons';
const { width } = Dimensions.get('window');
@@ -22,6 +23,8 @@ export default function ProductDetailScreen() {
const productId = typeof id === 'string' ? parseInt(id) : null;
const { product, loading, error } = useProduct(productId);
+ const { addToCart } = useCart();
+ const [isAddingToCart, setIsAddingToCart] = useState(false);
const formatPrice = (price: number) => {
return new Intl.NumberFormat('vi-VN', {
@@ -38,6 +41,19 @@ export default function ProductDetailScreen() {
});
};
+ const handleAddToCart = async () => {
+ if (!product || product.stockQuantity <= 0) {
+ return;
+ }
+
+ setIsAddingToCart(true);
+ try {
+ await addToCart(product.productId, 1);
+ } finally {
+ setIsAddingToCart(false);
+ }
+ };
+
if (loading) {
return (
@@ -167,16 +183,36 @@ export default function ProductDetailScreen() {
{/* Bottom Action Buttons */}
-
+ {isAddingToCart ? (
+
+ ) : (
+
+ )}
- {product.stockQuantity > 0 ? 'Thêm vào giỏ' : 'Hết hàng'}
+ {isAddingToCart
+ ? 'Đang thêm...'
+ : product.stockQuantity > 0
+ ? 'Thêm vào giỏ'
+ : 'Hết hàng'}
-
+
Mua ngay
@@ -344,6 +380,9 @@ const styles = StyleSheet.create({
fontWeight: '600',
color: '#fff',
},
+ actionButtonDisabled: {
+ opacity: 0.5,
+ },
loadingContainer: {
flex: 1,
justifyContent: 'center',
diff --git a/components/CartItemCard.tsx b/components/CartItemCard.tsx
new file mode 100644
index 0000000..71f91b9
--- /dev/null
+++ b/components/CartItemCard.tsx
@@ -0,0 +1,200 @@
+import React from 'react';
+import {
+ View,
+ Text,
+ Image,
+ StyleSheet,
+ TouchableOpacity,
+ Alert,
+} from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+import { CartItem } from '../services/cart';
+
+interface CartItemCardProps {
+ item: CartItem;
+ onUpdateQuantity: (cartItemId: number, quantity: number) => Promise;
+ onRemove: (cartItemId: number) => Promise;
+}
+
+export default function CartItemCard({
+ item,
+ onUpdateQuantity,
+ onRemove,
+}: CartItemCardProps) {
+ const handleDecrease = async () => {
+ if (item.quantity > 1) {
+ const success = await onUpdateQuantity(item.cartItemId, item.quantity - 1);
+ if (!success) {
+ Alert.alert('Lỗi', 'Không thể cập nhật số lượng');
+ }
+ } else {
+ Alert.alert(
+ 'Xác nhận',
+ 'Bạn có muốn xóa sản phẩm này khỏi giỏ hàng?',
+ [
+ { text: 'Hủy', style: 'cancel' },
+ {
+ text: 'Xóa',
+ onPress: async () => {
+ const success = await onRemove(item.cartItemId);
+ if (!success) {
+ Alert.alert('Lỗi', 'Không thể xóa sản phẩm');
+ }
+ },
+ style: 'destructive'
+ },
+ ]
+ );
+ }
+ };
+
+ const handleIncrease = async () => {
+ const success = await onUpdateQuantity(item.cartItemId, item.quantity + 1);
+ if (!success) {
+ Alert.alert('Lỗi', 'Không thể cập nhật số lượng');
+ }
+ };
+
+ const handleRemove = () => {
+ Alert.alert(
+ 'Xác nhận',
+ 'Bạn có chắc muốn xóa sản phẩm này?',
+ [
+ { text: 'Hủy', style: 'cancel' },
+ {
+ text: 'Xóa',
+ onPress: async () => {
+ const success = await onRemove(item.cartItemId);
+ if (!success) {
+ Alert.alert('Lỗi', 'Không thể xóa sản phẩm');
+ }
+ },
+ style: 'destructive'
+ },
+ ]
+ );
+ };
+
+ const formatPrice = (price: number) => {
+ return new Intl.NumberFormat('vi-VN', {
+ style: 'currency',
+ currency: 'VND',
+ }).format(price);
+ };
+
+ return (
+
+
+
+
+
+ {item.productName}
+
+
+ {formatPrice(item.price)}
+
+
+
+
+
+
+
+ {item.quantity}
+
+
+
+
+
+
+ {formatPrice(item.subtotal)}
+
+
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ backgroundColor: '#fff',
+ padding: 12,
+ marginBottom: 12,
+ borderRadius: 12,
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 2 },
+ shadowOpacity: 0.1,
+ shadowRadius: 4,
+ elevation: 3,
+ },
+ image: {
+ width: 80,
+ height: 80,
+ borderRadius: 8,
+ backgroundColor: '#f0f0f0',
+ },
+ info: {
+ flex: 1,
+ marginLeft: 12,
+ justifyContent: 'space-between',
+ },
+ name: {
+ fontSize: 15,
+ fontWeight: '600',
+ color: '#333',
+ marginBottom: 4,
+ },
+ price: {
+ fontSize: 14,
+ color: '#ff6b6b',
+ fontWeight: '500',
+ },
+ footer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ marginTop: 8,
+ },
+ quantityContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: '#f5f5f5',
+ borderRadius: 6,
+ paddingHorizontal: 4,
+ },
+ quantityButton: {
+ width: 28,
+ height: 28,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ quantity: {
+ fontSize: 15,
+ fontWeight: '600',
+ color: '#333',
+ minWidth: 30,
+ textAlign: 'center',
+ },
+ subtotal: {
+ fontSize: 16,
+ fontWeight: '700',
+ color: '#ff6b6b',
+ },
+ deleteButton: {
+ padding: 8,
+ justifyContent: 'flex-start',
+ },
+});
\ No newline at end of file
diff --git a/components/ProductCard.tsx b/components/ProductCard.tsx
index f925015..0a966be 100644
--- a/components/ProductCard.tsx
+++ b/components/ProductCard.tsx
@@ -1,7 +1,9 @@
-import React from 'react';
-import { View, Text, StyleSheet, TouchableOpacity, Image } from 'react-native';
+import React, { useState } from 'react';
+import { View, Text, StyleSheet, TouchableOpacity, Image, ActivityIndicator } from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
import { ProductResponse } from '../services/product';
import { useRouter } from 'expo-router';
+import { useCart } from '../hooks/useCart';
interface ProductCardProps {
product: ProductResponse;
@@ -9,11 +11,28 @@ interface ProductCardProps {
const ProductCard: React.FC = ({ product }) => {
const router = useRouter();
+ const { addToCart } = useCart();
+ const [isAdding, setIsAdding] = useState(false);
const handlePress = () => {
router.push(`/products/${product.productId}`);
};
+ const handleAddToCart = async (e: any) => {
+ e.stopPropagation(); // Ngăn chặn việc navigate khi click vào nút giỏ hàng
+
+ if (product.stockQuantity <= 0) {
+ return; // Không cho thêm vào giỏ nếu hết hàng
+ }
+
+ setIsAdding(true);
+ try {
+ await addToCart(product.productId, 1);
+ } finally {
+ setIsAdding(false);
+ }
+ };
+
const formatPrice = (price: number) => {
return new Intl.NumberFormat('vi-VN', {
style: 'currency',
@@ -23,29 +42,59 @@ const ProductCard: React.FC = ({ product }) => {
return (
-
+
+
+
+ {/* Category Badge */}
+
+ {product.categoryName}
+
+
+ {/* Out of Stock Overlay */}
+ {product.stockQuantity <= 0 && (
+
+ HẾT HÀNG
+
+ )}
+
+
{product.productName}
-
- {product.categoryName}
-
-
- {formatPrice(product.price)}
- {product.stockQuantity > 0 ? (
-
+
+
+
+ {formatPrice(product.price)}
+ {product.stockQuantity > 0 && (
Còn {product.stockQuantity}
-
- ) : (
-
- Hết hàng
-
- )}
+ )}
+
+
+ {/* Add to Cart Button */}
+
+ {isAdding ? (
+
+ ) : (
+
+ )}
+
@@ -65,11 +114,46 @@ const styles = StyleSheet.create({
elevation: 3,
overflow: 'hidden',
},
- image: {
+ imageContainer: {
+ position: 'relative',
width: '100%',
height: 200,
+ },
+ image: {
+ width: '100%',
+ height: '100%',
backgroundColor: '#f0f0f0',
},
+ categoryBadge: {
+ position: 'absolute',
+ top: 12,
+ left: 12,
+ backgroundColor: 'rgba(255, 107, 107, 0.95)',
+ paddingHorizontal: 10,
+ paddingVertical: 5,
+ borderRadius: 6,
+ },
+ categoryText: {
+ fontSize: 12,
+ color: '#fff',
+ fontWeight: '600',
+ },
+ outOfStockOverlay: {
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ outOfStockText: {
+ fontSize: 18,
+ fontWeight: '700',
+ color: '#fff',
+ letterSpacing: 2,
+ },
content: {
padding: 12,
},
@@ -77,39 +161,44 @@ const styles = StyleSheet.create({
fontSize: 16,
fontWeight: '600',
color: '#333',
- marginBottom: 4,
- },
- categoryName: {
- fontSize: 13,
- color: '#666',
marginBottom: 8,
+ minHeight: 44,
},
- priceContainer: {
+ footer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
+ priceSection: {
+ flex: 1,
+ },
price: {
fontSize: 18,
fontWeight: '700',
- color: '#e91e63',
- },
- stockBadge: {
- backgroundColor: '#4caf50',
- paddingHorizontal: 8,
- paddingVertical: 4,
- borderRadius: 4,
+ color: '#ff6b6b',
+ marginBottom: 2,
},
stockText: {
fontSize: 12,
- color: '#fff',
+ color: '#4caf50',
fontWeight: '500',
},
- outOfStock: {
- backgroundColor: '#f44336',
+ addButton: {
+ width: 44,
+ height: 44,
+ backgroundColor: '#ff6b6b',
+ borderRadius: 22,
+ justifyContent: 'center',
+ alignItems: 'center',
+ shadowColor: '#ff6b6b',
+ shadowOffset: { width: 0, height: 2 },
+ shadowOpacity: 0.3,
+ shadowRadius: 4,
+ elevation: 4,
},
- outOfStockText: {
- color: '#fff',
+ addButtonDisabled: {
+ backgroundColor: '#ccc',
+ shadowOpacity: 0,
},
});
diff --git a/hooks/useAuth.ts b/hooks/useAuth.ts
index e69de29..b433971 100644
--- a/hooks/useAuth.ts
+++ b/hooks/useAuth.ts
@@ -0,0 +1,106 @@
+import { useState, useEffect, useCallback } from 'react';
+import { Alert } from 'react-native';
+import * as authService from '../services/auth';
+
+interface User {
+ userId: number;
+ email: string;
+}
+
+export const useAuth = () => {
+ const [user, setUser] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
+
+ // Kiểm tra trạng thái đăng nhập khi khởi động
+ useEffect(() => {
+ checkAuthStatus();
+ }, []);
+
+ const checkAuthStatus = async () => {
+ try {
+ const authenticated = await authService.isAuthenticated();
+ setIsAuthenticated(authenticated);
+
+ if (authenticated) {
+ const userData = await authService.getUser();
+ setUser(userData);
+ }
+ } catch (error) {
+ console.error('Check auth status error:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const login = async (email: string, password: string) => {
+ try {
+ const response = await authService.login(email, password);
+
+ setUser({
+ userId: response.userId,
+ email: response.email,
+ });
+ setIsAuthenticated(true);
+
+ Alert.alert('Thành công', 'Đăng nhập thành công!');
+ return true;
+ } catch (error: any) {
+ console.error('Login error:', error);
+ Alert.alert('Lỗi', error.message || 'Đăng nhập thất bại');
+ return false;
+ }
+ };
+
+ const register = async (email: string, password: string) => {
+ try {
+ const response = await authService.register(email, password);
+
+ // Sau khi đăng ký thành công, tự động đăng nhập
+ if (response.token) {
+ await authService.saveAuthData(response);
+ setUser({
+ userId: response.userId,
+ email: response.email,
+ });
+ setIsAuthenticated(true);
+ }
+
+ Alert.alert('Thành công', 'Đăng ký thành công!');
+ return true;
+ } catch (error: any) {
+ console.error('Register error:', error);
+ Alert.alert('Lỗi', error.message || 'Đăng ký thất bại');
+ return false;
+ }
+ };
+
+ const logout = async () => {
+ try {
+ const token = await authService.getAuthToken();
+ if (token) {
+ await authService.logout(token);
+ }
+
+ setUser(null);
+ setIsAuthenticated(false);
+
+ Alert.alert('Thành công', 'Đăng xuất thành công!');
+ return true;
+ } catch (error: any) {
+ console.error('Logout error:', error);
+ Alert.alert('Lỗi', 'Đăng xuất thất bại');
+ return false;
+ }
+ };
+
+ return {
+ user,
+ loading,
+ isAuthenticated,
+ login,
+ register,
+ logout,
+ checkAuthStatus,
+ };
+};
\ No newline at end of file
diff --git a/hooks/useCart.ts b/hooks/useCart.ts
index e69de29..defec64 100644
--- a/hooks/useCart.ts
+++ b/hooks/useCart.ts
@@ -0,0 +1,162 @@
+import { useCallback, useEffect, useState } from 'react';
+import { Alert } from 'react-native';
+import * as authService from '../services/auth';
+import cartService, { AddToCartRequest, CartResponse } from '../services/cart';
+
+export const useCart = () => {
+ const [cart, setCart] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [refreshing, setRefreshing] = useState(false);
+
+ // Lấy userId từ auth service
+ const getUserId = async (): Promise => {
+ try {
+ const isAuthenticated = await authService.isAuthenticated();
+ if (!isAuthenticated) {
+ console.log('User is not authenticated');
+ return null;
+ }
+
+ const user = await authService.getUser();
+ console.log('getUserId - User data:', user);
+
+ if (user && typeof user.userId === 'number') {
+ return user.userId;
+ }
+
+ if (user) {
+ console.warn('User exists but userId is missing or invalid:', user);
+ }
+
+ return null;
+ } catch (error) {
+ console.error('Error getting userId:', error);
+ return null;
+ }
+ };
+
+ // Tải giỏ hàng
+ const loadCart = useCallback(async () => {
+ try {
+ setLoading(true);
+ const userId = await getUserId();
+ if (!userId) {
+ setCart(null);
+ return;
+ }
+
+ const data = await cartService.getCart(userId);
+ setCart(data);
+ } catch (error: any) {
+ console.error('Load cart error:', error);
+ const errorMessage = error?.message || '';
+
+ if (errorMessage.includes('404') || errorMessage.includes('Not Found') || errorMessage.includes('not found')) {
+ setCart(null);
+ } else if (!errorMessage.includes('User not logged in')) {
+ console.warn('Failed to load cart:', errorMessage);
+ }
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ // Refresh giỏ hàng
+ const refreshCart = useCallback(async () => {
+ try {
+ setRefreshing(true);
+ await loadCart();
+ } finally {
+ setRefreshing(false);
+ }
+ }, [loadCart]);
+
+ // Thêm sản phẩm vào giỏ hàng
+ const addToCart = useCallback(async (productId: number, quantity: number = 1) => {
+ try {
+ console.log('=== useCart: addToCart START ===', { productId, quantity });
+ const userId = await getUserId();
+ if (!userId) {
+ Alert.alert('Thông báo', 'Vui lòng đăng nhập để thêm vào giỏ hàng');
+ return false;
+ }
+
+ const updatedCart = await cartService.addToCart(userId, { productId, quantity });
+ console.log('=== useCart: addToCart SUCCESS - Setting cart ===', updatedCart);
+ setCart(updatedCart); // Cập nhật state ngay lập tức
+ Alert.alert('Thành công', 'Đã thêm sản phẩm vào giỏ hàng');
+ return true;
+ } catch (error: any) {
+ console.error('=== useCart: addToCart ERROR ===', error);
+ Alert.alert('Lỗi', 'Không thể thêm sản phẩm vào giỏ hàng');
+ return false;
+ }
+ }, []);
+
+ // Cập nhật số lượng sản phẩm
+ const updateQuantity = useCallback(async (cartItemId: number, quantity: number) => {
+ try {
+ const userId = await getUserId();
+ if (!userId) return false;
+
+ const updatedCart = await cartService.updateCartItem(userId, cartItemId, { quantity });
+ setCart(updatedCart); // Cập nhật state ngay lập tức
+ return true;
+ } catch (error) {
+ console.error('Update quantity error:', error);
+ Alert.alert('Lỗi', 'Không thể cập nhật số lượng');
+ return false;
+ }
+ }, []);
+
+ // Xóa một sản phẩm
+ const removeItem = useCallback(async (cartItemId: number) => {
+ try {
+ const userId = await getUserId();
+ if (!userId) return false;
+
+ const updatedCart = await cartService.removeCartItem(userId, cartItemId);
+ setCart(updatedCart); // Cập nhật state ngay lập tức
+ Alert.alert('Thành công', 'Đã xóa sản phẩm khỏi giỏ hàng');
+ return true;
+ } catch (error) {
+ console.error('Remove item error:', error);
+ Alert.alert('Lỗi', 'Không thể xóa sản phẩm');
+ return false;
+ }
+ }, []);
+
+ // Xóa tất cả sản phẩm
+ const clearCart = useCallback(async () => {
+ try {
+ const userId = await getUserId();
+ if (!userId) return false;
+
+ await cartService.clearCart(userId);
+ setCart(null);
+ Alert.alert('Thành công', 'Đã xóa tất cả sản phẩm');
+ return true;
+ } catch (error) {
+ console.error('Clear cart error:', error);
+ Alert.alert('Lỗi', 'Không thể xóa giỏ hàng');
+ return false;
+ }
+ }, []);
+
+ // Load cart khi component mount
+ useEffect(() => {
+ loadCart();
+ }, [loadCart]);
+
+ return {
+ cart,
+ loading,
+ refreshing,
+ loadCart,
+ refreshCart,
+ addToCart,
+ updateQuantity,
+ removeItem,
+ clearCart,
+ };
+};
\ No newline at end of file
diff --git a/services/api.ts b/services/api.ts
index e69de29..06ba9f0 100644
--- a/services/api.ts
+++ b/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 {
+ 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 {
+ 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(response: Response): Promise {
+ 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(endpoint: string, options: RequestOptions = {}): Promise {
+ 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(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(endpoint: string, data?: any, options: RequestOptions = {}): Promise {
+ 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(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(endpoint: string, data?: any, options: RequestOptions = {}): Promise {
+ 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(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(endpoint: string, options: RequestOptions = {}): Promise {
+ 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(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(endpoint: string, data?: any, options: RequestOptions = {}): Promise {
+ 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(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;
\ No newline at end of file
diff --git a/services/auth.ts b/services/auth.ts
index 75a1477..6d212ef 100644
--- a/services/auth.ts
+++ b/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 {
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 {
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 {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ try {
+ const token = await AsyncStorage.getItem('authToken');
+ const user = await AsyncStorage.getItem('user');
+ return !!(token && user);
+ } catch (error) {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/services/cart.ts b/services/cart.ts
index e69de29..0343794 100644
--- a/services/cart.ts
+++ b/services/cart.ts
@@ -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 => {
+ console.log('=== ADD TO CART REQUEST ===', { userId, data });
+ const response = await api.post(
+ `/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 => {
+ console.log('=== GET CART REQUEST ===', { userId });
+ const response = await api.get(
+ `/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 => {
+ console.log('=== UPDATE CART ITEM REQUEST ===', { userId, cartItemId, data });
+ const response = await api.put(
+ `/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 => {
+ console.log('=== REMOVE CART ITEM REQUEST ===', { userId, cartItemId });
+ const response = await api.delete(
+ `/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 => {
+ console.log('=== CLEAR CART REQUEST ===', { userId });
+ await api.delete(
+ `/cart/${userId}/clear`,
+ { requireAuth: true }
+ );
+ console.log('=== CLEAR CART SUCCESS ===');
+ },
+};
+
+export default cartService;
\ No newline at end of file
diff --git a/services/product.ts b/services/product.ts
index 601f145..884e8fc 100644
--- a/services/product.ts
+++ b/services/product.ts
@@ -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 {
- 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(`/products?${params}`);
}
/**
* Lấy chi tiết sản phẩm theo ID
*/
async getProductById(id: number): Promise {
- 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(`/products/${id}`);
}
/**
@@ -111,25 +59,14 @@ class ProductService {
sortBy: string = 'productId',
sortDir: 'asc' | 'desc' = 'asc'
): Promise {
- 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(`/products/category/${categoryId}?${params}`);
}
/**
@@ -142,26 +79,15 @@ class ProductService {
sortBy: string = 'productId',
sortDir: 'asc' | 'desc' = 'asc'
): Promise {
- 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(`/products/search?${params}`);
}
/**
@@ -173,25 +99,14 @@ class ProductService {
sortBy: string = 'productId',
sortDir: 'asc' | 'desc' = 'asc'
): Promise {
- 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(`/products/available?${params}`);
}
}