cart
This commit is contained in:
253
app/(tabs)/cart.tsx
Normal file
253
app/(tabs)/cart.tsx
Normal file
@@ -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 (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color="#ff6b6b" />
|
||||
<Text style={styles.loadingText}>Đang tải giỏ hàng...</Text>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
if (!cart || cart.items.length === 0) {
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.headerTitle}>Giỏ hàng</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.emptyContainer}>
|
||||
<Ionicons name="cart-outline" size={100} color="#ccc" />
|
||||
<Text style={styles.emptyText}>Giỏ hàng trống</Text>
|
||||
<Text style={styles.emptySubtext}>
|
||||
Hãy thêm sản phẩm vào giỏ hàng để mua sắm
|
||||
</Text>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.headerTitle}>Giỏ hàng</Text>
|
||||
<TouchableOpacity onPress={handleClearCart} style={styles.clearButton}>
|
||||
<Ionicons name="trash-outline" size={22} color="#ff4444" />
|
||||
<Text style={styles.clearText}>Xóa tất cả</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<FlatList
|
||||
data={cart.items}
|
||||
keyExtractor={(item) => item.cartItemId.toString()}
|
||||
renderItem={({ item }) => (
|
||||
<CartItemCard
|
||||
item={item}
|
||||
onUpdateQuantity={updateQuantity}
|
||||
onRemove={removeItem}
|
||||
/>
|
||||
)}
|
||||
contentContainerStyle={styles.listContent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={refreshCart}
|
||||
colors={['#ff6b6b']}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<View style={styles.footer}>
|
||||
<View style={styles.summaryRow}>
|
||||
<Text style={styles.summaryLabel}>Tổng số lượng:</Text>
|
||||
<Text style={styles.summaryValue}>{cart.totalItems} sản phẩm</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.summaryRow}>
|
||||
<Text style={styles.totalLabel}>Tổng tiền:</Text>
|
||||
<Text style={styles.totalValue}>
|
||||
{formatPrice(cart.totalAmount)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.checkoutButton}
|
||||
onPress={handleCheckout}
|
||||
>
|
||||
<Text style={styles.checkoutText}>Thanh toán</Text>
|
||||
<Ionicons name="arrow-forward" size={20} color="#fff" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
@@ -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 (
|
||||
<SafeAreaView style={styles.container}>
|
||||
@@ -167,16 +183,36 @@ export default function ProductDetailScreen() {
|
||||
{/* Bottom Action Buttons */}
|
||||
<View style={styles.bottomActions}>
|
||||
<TouchableOpacity
|
||||
style={[styles.actionButton, styles.addToCartButton]}
|
||||
disabled={product.stockQuantity === 0}
|
||||
style={[
|
||||
styles.actionButton,
|
||||
styles.addToCartButton,
|
||||
(product.stockQuantity === 0 || isAddingToCart) && styles.actionButtonDisabled
|
||||
]}
|
||||
onPress={handleAddToCart}
|
||||
disabled={product.stockQuantity === 0 || isAddingToCart}
|
||||
>
|
||||
<Ionicons name="cart-outline" size={24} color="#fff" />
|
||||
{isAddingToCart ? (
|
||||
<ActivityIndicator size="small" color="#fff" />
|
||||
) : (
|
||||
<Ionicons name="cart-outline" size={24} color="#fff" />
|
||||
)}
|
||||
<Text style={styles.actionButtonText}>
|
||||
{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'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity style={[styles.actionButton, styles.buyNowButton]}>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.actionButton,
|
||||
styles.buyNowButton,
|
||||
product.stockQuantity === 0 && styles.actionButtonDisabled
|
||||
]}
|
||||
disabled={product.stockQuantity === 0}
|
||||
>
|
||||
<Ionicons name="flash-outline" size={24} color="#fff" />
|
||||
<Text style={styles.actionButtonText}>Mua ngay</Text>
|
||||
</TouchableOpacity>
|
||||
@@ -344,6 +380,9 @@ const styles = StyleSheet.create({
|
||||
fontWeight: '600',
|
||||
color: '#fff',
|
||||
},
|
||||
actionButtonDisabled: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
loadingContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
|
||||
Reference in New Issue
Block a user