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 && (
+
+
+
+
+
-
+ )}
+
);
-}
+};
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',
},
-});
\ No newline at end of file
+ buttonContainer: {
+ marginTop: 30,
+ },
+});
+
+export default CartScreen;
diff --git a/app/screens/user/ProductDetailsScreen.tsx b/app/screens/user/ProductDetailsScreen.tsx
index c85d178..df4fba5 100644
--- a/app/screens/user/ProductDetailsScreen.tsx
+++ b/app/screens/user/ProductDetailsScreen.tsx
@@ -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(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 (
{/* Header */}
@@ -120,7 +138,7 @@ export default function ProductScreen() {
-
+
Ajouter au panier
@@ -270,4 +288,4 @@ const styles = StyleSheet.create({
fontSize: 16,
fontWeight: '600',
},
-});
\ No newline at end of file
+});
diff --git a/app/utils/cartUtils.tsx b/app/utils/cartUtils.tsx
new file mode 100644
index 0000000..3dd6435
--- /dev/null
+++ b/app/utils/cartUtils.tsx
@@ -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
+ );
+};
\ No newline at end of file