From befa166ef580b64f732b7e9f4bcdb7608a344dc3 Mon Sep 17 00:00:00 2001 From: Med Kamel Date: Fri, 18 Apr 2025 06:09:09 +0100 Subject: [PATCH] order submission v1 --- app/_layout.tsx | 3 + app/components/CartItem.tsx | 107 +++++++++++++++ app/components/ProductCard.tsx | 1 - app/components/QuantityControl.tsx | 86 ++++++++++++ app/constants/types.ts | 14 +- app/context/cartContext.tsx | 59 +++++++++ app/screens/auth/SignIn-Screen.tsx | 14 +- app/screens/user/CartScreen.tsx | 151 +++++++++++++++++----- app/screens/user/ProductDetailsScreen.tsx | 26 +++- app/utils/cartUtils.tsx | 22 ++++ 10 files changed, 437 insertions(+), 46 deletions(-) create mode 100644 app/components/CartItem.tsx create mode 100644 app/components/QuantityControl.tsx create mode 100644 app/context/cartContext.tsx create mode 100644 app/utils/cartUtils.tsx diff --git a/app/_layout.tsx b/app/_layout.tsx index 7614bcf..e40127a 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -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 ( + + ); diff --git a/app/components/CartItem.tsx b/app/components/CartItem.tsx new file mode 100644 index 0000000..73ff3a1 --- /dev/null +++ b/app/components/CartItem.tsx @@ -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 = ({ 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 ( + + + + + + + {item.name} + + + + + {item.price}{item.priceUnit} + + + + ); +}; + +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; diff --git a/app/components/ProductCard.tsx b/app/components/ProductCard.tsx index 881435e..f1469b1 100644 --- a/app/components/ProductCard.tsx +++ b/app/components/ProductCard.tsx @@ -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 ( diff --git a/app/components/QuantityControl.tsx b/app/components/QuantityControl.tsx new file mode 100644 index 0000000..3e42cf7 --- /dev/null +++ b/app/components/QuantityControl.tsx @@ -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 = ({ + 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 ( + + + + + + + {quantity} + + + + + + + ); +}; + +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; diff --git a/app/constants/types.ts b/app/constants/types.ts index 3d44f61..2c9ddb1 100644 --- a/app/constants/types.ts +++ b/app/constants/types.ts @@ -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; + } \ No newline at end of file diff --git a/app/context/cartContext.tsx b/app/context/cartContext.tsx new file mode 100644 index 0000000..704f06f --- /dev/null +++ b/app/context/cartContext.tsx @@ -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(undefined); + +export const CartProvider = ({ children }: { children: ReactNode }) => { + const [cart, setCart] = useState({ 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 ( + + {children} + + ); +}; + +export const useCart = () => { + const context = useContext(CartContext); + if (!context) { + throw new Error('useCart must be used within a CartProvider'); + } + return context; + }; + diff --git a/app/screens/auth/SignIn-Screen.tsx b/app/screens/auth/SignIn-Screen.tsx index a7d7bc6..c0d09b7 100644 --- a/app/screens/auth/SignIn-Screen.tsx +++ b/app/screens/auth/SignIn-Screen.tsx @@ -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 ( diff --git a/app/screens/user/CartScreen.tsx b/app/screens/user/CartScreen.tsx index 224d78b..c3b0544 100644 --- a/app/screens/user/CartScreen.tsx +++ b/app/screens/user/CartScreen.tsx @@ -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(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 Chargement...; // Loading state while checking authentication + } -export default function CartScreen() { return ( - - - router.back()}> - - - Mon Panier + + Votre Panier + + {cart.items.length === 0 ? ( + Votre panier est vide. + ) : ( + cart.items.map((item, index) => ( + + + {item.productName} - Quantité : {item.quantity} + + + )) + )} + + {cart.items.length > 0 && ( + +