order submission v1
This commit is contained in:
parent
83346c7ba5
commit
befa166ef5
@ -3,12 +3,15 @@ import { Stack } from 'expo-router';
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import React from 'react';
|
||||
import { CartProvider } from './context/cartContext'
|
||||
|
||||
export default function Layout() {
|
||||
return (
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<SafeAreaProvider>
|
||||
<CartProvider>
|
||||
<Stack screenOptions={{ headerShown: false}} />
|
||||
</CartProvider>
|
||||
</SafeAreaProvider>
|
||||
</GestureHandlerRootView>
|
||||
);
|
||||
|
107
app/components/CartItem.tsx
Normal file
107
app/components/CartItem.tsx
Normal file
@ -0,0 +1,107 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet, Image } from 'react-native';
|
||||
import COLORS from "@/app/constants/colors";
|
||||
import { CartItem as CartItemType } from '@/app/constants/types';
|
||||
import QuantityControl from '@/app/components/QuantityControl';
|
||||
import Animated, {
|
||||
useAnimatedStyle,
|
||||
withTiming,
|
||||
useSharedValue,
|
||||
runOnJS
|
||||
} from 'react-native-reanimated';
|
||||
|
||||
interface CartItemProps {
|
||||
item: CartItemType;
|
||||
onUpdateQuantity: (id: string, quantity: number) => void;
|
||||
}
|
||||
|
||||
const CartItem: React.FC<CartItemProps> = ({ item, onUpdateQuantity }) => {
|
||||
const opacity = useSharedValue(1);
|
||||
const scale = useSharedValue(1);
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
opacity: opacity.value,
|
||||
transform: [{ scale: scale.value }]
|
||||
};
|
||||
});
|
||||
|
||||
const handleIncrease = () => {
|
||||
scale.value = withTiming(1.02, { duration: 100 }, () => {
|
||||
scale.value = withTiming(1, { duration: 100 });
|
||||
});
|
||||
onUpdateQuantity(item.id, item.quantity + 1);
|
||||
};
|
||||
|
||||
const handleDecrease = () => {
|
||||
if (item.quantity > 0) {
|
||||
scale.value = withTiming(0.98, { duration: 100 }, () => {
|
||||
scale.value = withTiming(1, { duration: 100 });
|
||||
});
|
||||
onUpdateQuantity(item.id, item.quantity - 1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Animated.View style={[styles.container, animatedStyle]}>
|
||||
<View style={styles.imageContainer}>
|
||||
<Image source={{ uri: item.image }} style={styles.image} />
|
||||
</View>
|
||||
|
||||
<View style={styles.contentContainer}>
|
||||
<Text style={styles.name}>{item.name}</Text>
|
||||
|
||||
<View style={styles.controlRow}>
|
||||
<QuantityControl
|
||||
quantity={item.quantity}
|
||||
onIncrease={handleIncrease}
|
||||
onDecrease={handleDecrease}
|
||||
/>
|
||||
|
||||
<Text style={styles.price}>{item.price}{item.priceUnit}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
backgroundColor: COLORS.secondary,
|
||||
borderRadius: 12,
|
||||
marginBottom: 5,
|
||||
padding: 4,
|
||||
},
|
||||
imageContainer: {
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
marginRight: 2,
|
||||
},
|
||||
image: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
name: {
|
||||
fontSize: 16,
|
||||
color: COLORS.text,
|
||||
marginBottom: 5,
|
||||
},
|
||||
controlRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
price: {
|
||||
fontSize: 14,
|
||||
color: COLORS.secondary,
|
||||
},
|
||||
});
|
||||
|
||||
export default CartItem;
|
@ -22,7 +22,6 @@ export default function ProductCard({
|
||||
inStock = true,
|
||||
}: ProductCardProps) {
|
||||
const router = useRouter();
|
||||
console.log('ProductId:', productName); // Changed to console.log for debugging
|
||||
|
||||
|
||||
return (
|
||||
|
86
app/components/QuantityControl.tsx
Normal file
86
app/components/QuantityControl.tsx
Normal file
@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import { View, Text, TouchableOpacity, StyleSheet, Platform } from 'react-native';
|
||||
import COLORS from "@/app/constants/colors";
|
||||
import { Minus, Plus } from 'lucide-react-native';
|
||||
import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated';
|
||||
import * as Haptics from 'expo-haptics';
|
||||
|
||||
interface QuantityControlProps {
|
||||
quantity: number;
|
||||
onIncrease: () => void;
|
||||
onDecrease: () => void;
|
||||
}
|
||||
|
||||
const QuantityControl: React.FC<QuantityControlProps> = ({
|
||||
quantity,
|
||||
onIncrease,
|
||||
onDecrease
|
||||
}) => {
|
||||
const animatedTextStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
opacity: 1,
|
||||
};
|
||||
});
|
||||
|
||||
const handleIncrease = () => {
|
||||
onIncrease();
|
||||
if (Platform.OS !== 'web') {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDecrease = () => {
|
||||
if (quantity > 0) {
|
||||
onDecrease();
|
||||
if (Platform.OS !== 'web') {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<TouchableOpacity
|
||||
style={[styles.button, { opacity: quantity <= 0 ? 0.5 : 1 }]}
|
||||
onPress={handleDecrease}
|
||||
disabled={quantity <= 0}
|
||||
>
|
||||
<Minus size={16} color={COLORS.text} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<Animated.Text style={[styles.quantity, animatedTextStyle]}>
|
||||
{quantity}
|
||||
</Animated.Text>
|
||||
|
||||
<TouchableOpacity style={styles.button} onPress={handleIncrease}>
|
||||
<Plus size={16} color={COLORS.text} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: 36,
|
||||
},
|
||||
button: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
backgroundColor: COLORS.secondary,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderRadius: 6,
|
||||
},
|
||||
quantity: {
|
||||
fontSize: 14,
|
||||
color: COLORS.text,
|
||||
paddingHorizontal: 6,
|
||||
minWidth: 40,
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
export default QuantityControl;
|
@ -6,4 +6,16 @@ export interface Product {
|
||||
price: string;
|
||||
inStock: boolean;
|
||||
}
|
||||
|
||||
export interface CartItem {
|
||||
id: string;
|
||||
name: string;
|
||||
price: number;
|
||||
priceUnit: string;
|
||||
quantity: number;
|
||||
image: string;
|
||||
}
|
||||
|
||||
export interface CartState {
|
||||
items: CartItem[];
|
||||
total: number;
|
||||
}
|
59
app/context/cartContext.tsx
Normal file
59
app/context/cartContext.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
// context/CartContext.tsx
|
||||
import React, { createContext, useState, useContext, ReactNode } from 'react';
|
||||
|
||||
type CartItem = {
|
||||
productName: string;
|
||||
quantity: number;
|
||||
};
|
||||
|
||||
type CartState = {
|
||||
items: CartItem[];
|
||||
total: number;
|
||||
};
|
||||
|
||||
type CartContextType = {
|
||||
cart: CartState;
|
||||
addItem: (item: CartItem) => void;
|
||||
clearCart: () => void;
|
||||
};
|
||||
|
||||
const CartContext = createContext<CartContextType | undefined>(undefined);
|
||||
|
||||
export const CartProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [cart, setCart] = useState<CartState>({ items: [], total: 0 });
|
||||
|
||||
const addItem = (item: CartItem) => {
|
||||
setCart(prev => {
|
||||
const existing = prev.items.find(i => i.productName === item.productName);
|
||||
let updatedItems;
|
||||
if (existing) {
|
||||
updatedItems = prev.items.map(i =>
|
||||
i.productName === item.productName
|
||||
? { ...i, quantity: i.quantity + item.quantity }
|
||||
: i
|
||||
);
|
||||
} else {
|
||||
updatedItems = [...prev.items, item];
|
||||
}
|
||||
const updatedTotal = updatedItems.reduce((sum, i) => sum + i.quantity, 0);
|
||||
return { items: updatedItems, total: updatedTotal };
|
||||
});
|
||||
};
|
||||
|
||||
const clearCart = () => setCart({ items: [], total: 0 });
|
||||
|
||||
return (
|
||||
<CartContext.Provider value={{ cart, addItem, clearCart }}>
|
||||
{children}
|
||||
</CartContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useCart = () => {
|
||||
const context = useContext(CartContext);
|
||||
if (!context) {
|
||||
throw new Error('useCart must be used within a CartProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
@ -42,15 +42,15 @@ const SignInScreen = () => {
|
||||
};
|
||||
|
||||
const handleLogin = async () => {
|
||||
// if (!validateForm()) return;
|
||||
if (!validateForm()) return;
|
||||
|
||||
//try {
|
||||
// const { user } = await signIn(form.email, form.password); // Destructure to get user
|
||||
// console.log("Connexion réussie :", user.email); // Access the email directly
|
||||
try {
|
||||
const { user } = await signIn(form.email, form.password); // Destructure to get user
|
||||
console.log("Connexion réussie :", user.email); // Access the email directly
|
||||
router.replace("/screens/user/UserHomeScreen");
|
||||
//} catch (error: any) {
|
||||
// Alert.alert("Erreur", error.message); // Display the error message
|
||||
//}
|
||||
} catch (error: any) {
|
||||
Alert.alert("Erreur", error.message); // Display the error message
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -1,46 +1,131 @@
|
||||
import { View, Text, StyleSheet, ScrollView,TouchableOpacity } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { ArrowLeft } from 'lucide-react-native';
|
||||
import { router } from "expo-router";
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { View, Text, Button, Alert, StyleSheet } from 'react-native';
|
||||
import { getAuth } from 'firebase/auth';
|
||||
import { collection, addDoc } from 'firebase/firestore'; // Make sure these are imported
|
||||
import { db } from '@/firebase/config'; // Ensure this path is correct
|
||||
import { useCart } from '@/app/context/cartContext';
|
||||
|
||||
const CartScreen = () => {
|
||||
const { cart, clearCart } = useCart();
|
||||
const auth = getAuth();
|
||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
|
||||
const router = useRouter();
|
||||
|
||||
// Check if the user is logged in when the component mounts
|
||||
useEffect(() => {
|
||||
const user = auth.currentUser;
|
||||
if (user) {
|
||||
setIsAuthenticated(true);
|
||||
} else {
|
||||
setIsAuthenticated(false);
|
||||
router.push('../screens/auth/SignInScreen'); // Redirect to sign-in screen if not logged in
|
||||
}
|
||||
}, [auth, router]);
|
||||
|
||||
const handleConfirmOrder = async () => {
|
||||
if (!isAuthenticated) {
|
||||
Alert.alert('Erreur', 'Utilisateur non connecté.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (cart.total <= 0) {
|
||||
Alert.alert('Panier vide', 'Veuillez ajouter des produits à votre panier');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const userId = auth.currentUser?.uid;
|
||||
if (!userId) {
|
||||
Alert.alert('Erreur', 'Utilisateur non connecté.');
|
||||
return;
|
||||
}
|
||||
|
||||
const orderData: any = {
|
||||
userId,
|
||||
status: 'En attente',
|
||||
};
|
||||
|
||||
cart.items.forEach((item, index) => {
|
||||
orderData[`item${index + 1}`] = [item.quantity, item.productName];
|
||||
});
|
||||
|
||||
await addDoc(collection(db, 'orders'), orderData);
|
||||
|
||||
Alert.alert('Commande confirmée', 'Votre commande a été enregistrée.');
|
||||
clearCart();
|
||||
} catch (error) {
|
||||
console.error('Erreur de commande', error);
|
||||
Alert.alert('Erreur', "Impossible d'enregistrer la commande.");
|
||||
}
|
||||
};
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return <Text>Chargement...</Text>; // Loading state while checking authentication
|
||||
}
|
||||
|
||||
export default function CartScreen() {
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity style={styles.backButton} onPress={() => router.back()}>
|
||||
<ArrowLeft size={24} color="#666" />
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.headerTitle}>Mon Panier</Text>
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>Votre Panier</Text>
|
||||
|
||||
{cart.items.length === 0 ? (
|
||||
<Text style={styles.empty}>Votre panier est vide.</Text>
|
||||
) : (
|
||||
cart.items.map((item, index) => (
|
||||
<View key={index} style={styles.itemContainer}>
|
||||
<Text style={styles.itemText}>
|
||||
{item.productName} - Quantité : {item.quantity}
|
||||
</Text>
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
|
||||
{cart.items.length > 0 && (
|
||||
<View style={styles.buttonContainer}>
|
||||
<Button title="Confirmer la commande" onPress={handleConfirmOrder} color="#8B4513" />
|
||||
<View style={{ marginTop: 10 }}>
|
||||
<Button title="Vider le panier" onPress={clearCart} color="#A52A2A" />
|
||||
</View>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
padding: 20,
|
||||
backgroundColor: '#FFF',
|
||||
flexGrow: 1,
|
||||
},
|
||||
header: {
|
||||
height: 60,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
position: 'relative',
|
||||
backgroundColor: '#fff',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#eee',
|
||||
marginTop:5,
|
||||
title: {
|
||||
fontSize: 22,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 15,
|
||||
textAlign: 'center',
|
||||
color: '#5A3E36',
|
||||
},
|
||||
backButton: {
|
||||
position: 'absolute',
|
||||
left: 20,
|
||||
top: '50%',
|
||||
transform: [{ translateY: -12 }],
|
||||
empty: {
|
||||
fontSize: 16,
|
||||
color: '#777',
|
||||
textAlign: 'center',
|
||||
marginTop: 50,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: '600',
|
||||
itemContainer: {
|
||||
backgroundColor: '#F5F5DC',
|
||||
padding: 15,
|
||||
marginBottom: 10,
|
||||
borderRadius: 10,
|
||||
borderColor: '#DDD',
|
||||
borderWidth: 1,
|
||||
},
|
||||
itemText: {
|
||||
fontSize: 16,
|
||||
color: '#333',
|
||||
},
|
||||
});
|
||||
buttonContainer: {
|
||||
marginTop: 30,
|
||||
},
|
||||
});
|
||||
|
||||
export default CartScreen;
|
||||
|
@ -1,10 +1,11 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { View, Text, StyleSheet, Image, TouchableOpacity, ActivityIndicator } from 'react-native';
|
||||
import { View, Text, StyleSheet, Image, TouchableOpacity, ActivityIndicator,Alert } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { useLocalSearchParams, useRouter } from 'expo-router';
|
||||
import { ChevronLeft, Minus, Plus } from 'lucide-react-native';
|
||||
import { doc, getDoc } from 'firebase/firestore';
|
||||
import { db } from '@/firebase/config';
|
||||
import { useCart } from '@/app/context/cartContext';
|
||||
import { Product } from '@/app/constants/types';
|
||||
|
||||
export default function ProductScreen() {
|
||||
@ -14,8 +15,10 @@ export default function ProductScreen() {
|
||||
const [quantity, setQuantity] = useState(1);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
|
||||
// Cart items state
|
||||
const [cartItems, setCartItems] = useState<{ productName: string; quantity: number }[]>([]);
|
||||
|
||||
const fetchProductDetails = async () => {
|
||||
if (!productId) {
|
||||
setError('Aucun ID de produit fourni.');
|
||||
@ -48,6 +51,21 @@ export default function ProductScreen() {
|
||||
fetchProductDetails();
|
||||
}, [productId]);
|
||||
|
||||
// Add item to cart
|
||||
const { addItem } = useCart();
|
||||
|
||||
const handleAddToCart = () => {
|
||||
if (!product) return;
|
||||
|
||||
addItem({
|
||||
productName: product?.productName,
|
||||
quantity: quantity,
|
||||
});
|
||||
|
||||
Alert.alert('Ajouté', `${quantity} ${product.productName} ajouté au panier`);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
{/* Header */}
|
||||
@ -120,7 +138,7 @@ export default function ProductScreen() {
|
||||
</View>
|
||||
|
||||
<View style={styles.footer}>
|
||||
<TouchableOpacity style={styles.addButton}>
|
||||
<TouchableOpacity style={styles.addButton} onPress={handleAddToCart}>
|
||||
<Text style={styles.addButtonText}>Ajouter au panier</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
@ -270,4 +288,4 @@ const styles = StyleSheet.create({
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
22
app/utils/cartUtils.tsx
Normal file
22
app/utils/cartUtils.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { CartItem } from '@/app/constants/types';
|
||||
|
||||
export const calculateTotal = (items: CartItem[]): number => {
|
||||
return items.reduce((total, item) => {
|
||||
return total + item.price * item.quantity;
|
||||
}, 0);
|
||||
};
|
||||
|
||||
export const updateItemQuantity = (
|
||||
items: CartItem[],
|
||||
itemId: string,
|
||||
newQuantity: number
|
||||
): CartItem[] => {
|
||||
// Don't allow negative quantities
|
||||
if (newQuantity < 0) {
|
||||
return items;
|
||||
}
|
||||
|
||||
return items.map((item) =>
|
||||
item.id === itemId ? { ...item, quantity: newQuantity } : item
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue
Block a user