Cart Logic v2

This commit is contained in:
Med Kamel 2025-04-23 03:41:33 +01:00
parent befa166ef5
commit eb23fd4696
22 changed files with 507 additions and 284 deletions

View File

@ -3,7 +3,7 @@ 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'
import { CartProvider } from '../context/cartContext'
export default function Layout() {
return (

View File

@ -1,90 +1,38 @@
import React from 'react';
import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native';
import React, { useEffect, useState } from 'react';
import { View, ActivityIndicator } from 'react-native';
import { getAuth, onAuthStateChanged } from 'firebase/auth';
import { router } from 'expo-router';
import COLORS from './constants/colors';
import { StatusBar } from 'expo-status-bar';
import OpeningScreen from './screens/auth/OpeningScreen'; // ajuste le chemin si besoin
const Index = () => {
const [checkingAuth, setCheckingAuth] = useState(true);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const OpeningScreen = () => {
useEffect(() => {
const auth = getAuth();
return (
<View style={styles.container}>
<StatusBar style="dark" />
<Image source={require('../assets/images/logo.png')} style={styles.logo} />
<Text style={styles.welcomeText}>Bienvenue chez Brix Café</Text>
<Image source={require('../assets/images/coffee_cup.jpg')} style={styles.coffeeImage} />
<Text style={styles.descriptionText}>
Depuis 2024, Brix Café vous fait vivre une expérience café unique,
inspirée du savoir-faire italien et portée par une passion authentique.
Des grains dexception, une qualité incomparable.
</Text>
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
setIsLoggedIn(true);
router.replace('/screens/user/UserHomeScreen'); // redirige vers Home
} else {
setIsLoggedIn(false); // utilisateur non connecté
}
setCheckingAuth(false); // vérification terminée
});
<TouchableOpacity style={styles.signInButton} onPress={() => router.push('/screens/auth/SignIn-Screen')}>
<Text style={styles.buttonText}>Se connecter</Text>
</TouchableOpacity>
return () => unsubscribe();
}, []);
<TouchableOpacity style={styles.signUpButton} onPress={() => router.push('/screens/auth/SignUpScreen')}>
<Text style={styles.buttonText}>Créer un compte</Text>
</TouchableOpacity>
</View>
);
if (checkingAuth) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#000' }}>
<ActivityIndicator size="large" color="#fff" />
</View>
);
}
return !isLoggedIn ? <OpeningScreen /> : null;
};
// Styles
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000000',
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 20,
},
logo: {
width: 120,
height: 120,
marginBottom: 20,
},
welcomeText: {
fontSize: 26,
fontWeight: 'bold',
color: COLORS.text,
marginBottom: 20,
},
coffeeImage: {
width: '120%',
height: 150,
marginTop:40,
marginBottom: 30,
},
descriptionText: {
fontSize: 14,
color: COLORS.text,
textAlign: 'center',
marginBottom: 40,
},
signInButton: {
backgroundColor: COLORS.primary,
paddingVertical: 15,
paddingHorizontal: 40,
borderRadius: 10,
marginBottom: 20,
width: '80%',
alignItems: 'center',
},
signUpButton: {
borderWidth: 1,
borderColor: COLORS.primary,
paddingVertical: 15,
paddingHorizontal: 40,
borderRadius: 10,
width: '80%',
alignItems: 'center',
},
buttonText: {
fontSize: 16,
color: COLORS.text,
fontWeight: 'bold',
},
});
export default OpeningScreen;
export default Index;

View File

@ -0,0 +1,90 @@
import React from 'react';
import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native';
import { router } from 'expo-router';
import COLORS from '@/constants/colors';
import { StatusBar } from 'expo-status-bar';
const OpeningScreen = () => {
return (
<View style={styles.container}>
<StatusBar style="inverted" />
<Image source={require('@/assets/images/logo.png')} style={styles.logo} />
<Text style={styles.welcomeText}>Bienvenue chez Brix Café</Text>
<Image source={require('@/assets/images/coffee_cup.jpg')} style={styles.coffeeImage} />
<Text style={styles.descriptionText}>
Depuis 2024, Brix Café vous fait vivre une expérience café unique,
inspirée du savoir-faire italien et portée par une passion authentique.
Des grains dexception, une qualité incomparable.
</Text>
<TouchableOpacity style={styles.signInButton} onPress={() => router.push('/screens/auth/SignIn-Screen')}>
<Text style={styles.buttonText}>Se connecter</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.signUpButton} onPress={() => router.push('/screens/auth/SignUpScreen')}>
<Text style={styles.buttonText}>Créer un compte</Text>
</TouchableOpacity>
</View>
);
};
// Styles
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000000',
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 20,
},
logo: {
width: 120,
height: 120,
marginBottom: 20,
},
welcomeText: {
fontSize: 26,
fontWeight: 'bold',
color: COLORS.text,
marginBottom: 20,
},
coffeeImage: {
width: '120%',
height: 150,
marginTop:40,
marginBottom: 30,
},
descriptionText: {
fontSize: 14,
color: COLORS.text,
textAlign: 'center',
marginBottom: 40,
},
signInButton: {
backgroundColor: COLORS.primary,
paddingVertical: 15,
paddingHorizontal: 40,
borderRadius: 10,
marginBottom: 20,
width: '80%',
alignItems: 'center',
},
signUpButton: {
borderWidth: 1,
borderColor: COLORS.primary,
paddingVertical: 15,
paddingHorizontal: 40,
borderRadius: 10,
width: '80%',
alignItems: 'center',
},
buttonText: {
fontSize: 16,
color: COLORS.text,
fontWeight: 'bold',
},
});
export default OpeningScreen;

View File

@ -5,8 +5,8 @@ import { router } from "expo-router";
import { signIn } from "../../../firebase/auth"; // Assure-toi que le chemin est correct
import { Link } from "expo-router";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view"; // Import the library
import COLORS from "@/app/constants/colors";
import COLORS from "@/constants/colors";
import AsyncStorage from "@react-native-async-storage/async-storage";
const SignInScreen = () => {
@ -43,13 +43,21 @@ const SignInScreen = () => {
const handleLogin = async () => {
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
const { user } = await signIn(form.email, form.password);
console.log("Connexion réussie :", user.email);
if (form.rememberMe) {
await AsyncStorage.setItem("rememberMe", "true");
} else {
await AsyncStorage.removeItem("rememberMe");
}
router.replace("/screens/user/UserHomeScreen");
} catch (error: any) {
Alert.alert("Erreur", error.message); // Display the error message
Alert.alert("Erreur", error.message);
}
};
@ -67,7 +75,7 @@ const SignInScreen = () => {
>
<ArrowLeft size={24} color="#666" />
</TouchableOpacity>
<Image source={require('../../../assets/images/logo.png')} style={styles.logo}/>
<Image source={require('@/assets/images/logo.png')} style={styles.logo}/>
<Text style={styles.title}>Se connecter</Text>
@ -117,9 +125,9 @@ const SignInScreen = () => {
style={styles.eyeIcon}
>
{showPassword ? (
<EyeOff size={20} color="#666" />
) : (
<Eye size={20} color="#666" />
) : (
<EyeOff size={20} color="#666" />
)}
</Pressable>
</View>

View File

@ -1,131 +1,238 @@
import React, { useEffect, useState } from 'react';
import { useRouter } from 'expo-router';
import { View, Text, Button, Alert, StyleSheet } from 'react-native';
import React, { useEffect, useState, useCallback } from 'react';
import {
View,
Text,
Alert,
StyleSheet,
FlatList,
Platform,
TouchableOpacity,
Button,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useCart } from '@/context/cartContext';
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';
import { collection, addDoc } from 'firebase/firestore';
import { db } from '@/firebase/config';
import CartItem from '@/components/CartItem';
import { ArrowLeft } from 'lucide-react-native';
import Animated, {
useAnimatedStyle,
useSharedValue,
withSequence,
withTiming,
} from 'react-native-reanimated';
import * as Haptics from 'expo-haptics';
import { useRouter } from 'expo-router';
import COLORS from '@/constants/colors';
import { CartItem as CartItemType } from '@/context/cartContext';
const CartScreen = () => {
const { cart, clearCart } = useCart();
export default function CartScreen() {
const { cart, clearCart, updateQuantity } = useCart();
const totalAmount = cart.total;
const [isProcessingOrder, setIsProcessingOrder] = useState(false);
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 totalValue = useSharedValue(1);
const buttonScale = useSharedValue(1);
const handleConfirmOrder = async () => {
if (!isAuthenticated) {
Alert.alert('Erreur', 'Utilisateur non connecté.');
return;
}
if (cart.total <= 0) {
const totalAnimatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: totalValue.value }],
}));
const handleConfirmOrder = useCallback(async () => {
if (totalAmount <= 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;
}
setIsProcessingOrder(true);
buttonScale.value = withSequence(
withTiming(0.95, { duration: 100 }),
withTiming(1, { duration: 150 })
);
if (Platform.OS !== 'web') {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
}
try {
const orderData: any = {
userId,
status: 'En attente',
createdAt: new Date().toISOString(),
totalAmount: totalAmount
};
cart.items.forEach((item, index) => {
orderData[`item${index + 1}`] = [item.quantity, item.productName];
orderData[`item${index + 1}`] = {
name: item.name,
quantity: item.quantity,
};
});
await addDoc(collection(db, 'orders'), orderData);
Alert.alert('Commande confirmée', 'Votre commande a été enregistrée.');
Alert.alert('Commande confirmée', `Montant Total : ${totalAmount}DT`);
clearCart();
} catch (error) {
console.error('Erreur de commande', error);
console.error('Erreur commande :', error);
Alert.alert('Erreur', "Impossible d'enregistrer la commande.");
} finally {
setIsProcessingOrder(false);
}
};
}, [cart, totalAmount]);
if (!isAuthenticated) {
return <Text>Chargement...</Text>; // Loading state while checking authentication
}
const renderItem = useCallback(({ item }: { item: CartItemType }) => (
<CartItem
item={item}
onUpdateQuantity={(id, newQty) => {
updateQuantity(id, newQty);
totalValue.value = withSequence(
withTiming(1.05, { duration: 100 }),
withTiming(1, { duration: 100 })
);
}}
/>
), []);
const keyExtractor = useCallback((item: CartItemType) => item.id, []);
return (
<View style={styles.container}>
<Text style={styles.title}>Votre Panier</Text>
<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>
{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>
))
)}
<View style={styles.content}>
<FlatList
data={cart.items}
renderItem={renderItem}
keyExtractor={keyExtractor}
showsVerticalScrollIndicator={false}
ListEmptyComponent={
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>Votre panier est vide</Text>
</View>
}
/>
</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 style={styles.footer}>
<View style={styles.totalContainer}>
<Text style={styles.totalLabel}>Total : </Text>
<Animated.Text style={[styles.totalAmount, totalAnimatedStyle]}>
{totalAmount}DT
</Animated.Text>
</View>
)}
</View>
<TouchableOpacity style={styles.ConfirmButton} onPress={handleConfirmOrder} disabled={totalAmount <= 0}>
<Text style={styles.ConfirmButtonText}>Ajouter au panier</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.clearCartButton}
onPress={() => {
Alert.alert(
'Vider le panier',
'Êtes-vous sûr de vouloir supprimer tous les articles du panier ?',
[
{ text: 'Annuler', style: 'cancel' },
{ text: 'Confirmer', style: 'destructive', onPress: clearCart },
]
);
}}
disabled={cart.items.length === 0}>
<Text style={styles.clearCartButtonText}>Vider le panier</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
};
}
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#FFF',
flexGrow: 1,
container: { flex: 1, backgroundColor: '#fff' },
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,
},
itemContainer: {
backgroundColor: '#F5F5DC',
padding: 15,
marginBottom: 10,
borderRadius: 10,
borderColor: '#DDD',
borderWidth: 1,
},
itemText: {
fontSize: 16,
headerTitle: {
fontSize: 20,
fontWeight: '600',
color: '#333',
},
buttonContainer: {
marginTop: 30,
content: {
flex: 1,
padding: 5,
},
footer: {
paddingHorizontal: 20,
paddingVertical: 10,
backgroundColor: COLORS.background_user,
borderTopWidth: 1,
borderTopColor: COLORS.secondary,
},
totalContainer: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
marginBottom: 10,
},
totalLabel: {
fontSize: 20,
},
totalAmount: {
fontSize: 20,
color: COLORS.primary
},
ConfirmButton: {
backgroundColor: '#B07B4F',
borderRadius: 12,
padding: 16,
alignItems: 'center',
},
ConfirmButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
clearCartButton: {
backgroundColor: '#D9534F',
borderRadius: 12,
padding: 16,
alignItems: 'center',
marginTop: 10, // Adding some space above the button
},
clearCartButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
emptyContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 10,
},
emptyText: {
fontSize: 18,
},
});
export default CartScreen;

View File

@ -2,12 +2,12 @@ import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-nati
import { SafeAreaView } from 'react-native-safe-area-context';
import { ArrowLeft } from 'lucide-react-native';
import { router } from "expo-router";
import ProductCard from '../../components/ProductCard';
import COLORS from '@/app/constants/colors';
import ProductCard from '@/components/ProductCard';
import COLORS from '@/constants/colors';
import { collection, getDocs } from 'firebase/firestore';
import { useEffect, useState } from 'react';
import { db } from '@/firebase/config';
import { Product } from '@/app/constants/types';
import { Product } from '@/constants/types';
export default function GrainsScreen() {
const [products, setProducts] = useState<Product[]>([]);
@ -74,7 +74,7 @@ const styles = StyleSheet.create({
},
content: {
flex: 1,
padding: 28,
padding: 30,
backgroundColor: COLORS.background_user,
},
});

View File

@ -5,8 +5,8 @@ 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';
import { useCart } from '@/context/cartContext';
import { Product } from '@/constants/types';
export default function ProductScreen() {
const { productId } = useLocalSearchParams();
@ -55,12 +55,18 @@ export default function ProductScreen() {
const { addItem } = useCart();
const handleAddToCart = () => {
if (!product) return;
if (!product) return;
addItem({
productName: product?.productName,
quantity: quantity,
});
addItem({
id: product.id,
name: product.productName,
quantity: quantity,
price: parseFloat(product.price), // Assure-toi que price est bien un number
image: product.image,
});
Alert.alert('Ajouté', `${quantity} ${product.productName} ajouté au panier`);
};
@ -70,7 +76,7 @@ const handleAddToCart = () => {
<SafeAreaView style={styles.container}>
{/* Header */}
<View style={styles.header}>
<TouchableOpacity onPress={() => router.back()}>
<TouchableOpacity onPress={() => router.push('/screens/user/GrainsScreen')}>
<ChevronLeft size={24} color="#333" />
</TouchableOpacity>
<Text style={styles.title}>{product?.productName || 'Détails du produit'}</Text>

View File

@ -1,18 +1,48 @@
import { View, Text, StyleSheet, ScrollView,TouchableOpacity } from 'react-native';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Alert } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { ArrowLeft } from 'lucide-react-native';
import { router } from "expo-router";
import { getAuth, signOut } from "firebase/auth";
export default function ProfileScreen() {
const handleSignOut = () => {
Alert.alert(
"Déconnexion",
"Êtes-vous sûr de vouloir vous déconnecter ?",
[
{ text: "Annuler", style: "cancel" },
{
text: "Se déconnecter",
style: "destructive",
onPress: async () => {
try {
const auth = getAuth();
await signOut(auth);
router.replace('/'); // Replace with your actual sign-in screen route
} catch (error) {
Alert.alert("Erreur", "Impossible de se déconnecter.");
console.error("Sign out error:", error);
}
}
}
]
);
};
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}>Espace Personel</Text>
</View>
<Text style={styles.headerTitle}>Espace Personnel</Text>
</View>
<View style={styles.body}>
<TouchableOpacity style={styles.signOutButton} onPress={handleSignOut}>
<Text style={styles.signOutButtonText}>Se déconnecter</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
@ -30,7 +60,7 @@ const styles = StyleSheet.create({
backgroundColor: '#fff',
borderBottomWidth: 1,
borderBottomColor: '#eee',
marginTop:5,
marginTop: 5,
},
backButton: {
position: 'absolute',
@ -43,4 +73,20 @@ const styles = StyleSheet.create({
fontWeight: '600',
color: '#333',
},
});
body: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
signOutButton: {
backgroundColor: '#B07B4F',
paddingVertical: 12,
paddingHorizontal: 24,
borderRadius: 10,
},
signOutButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
});

View File

@ -1,12 +1,23 @@
import { View, Text, StyleSheet, Image, } from 'react-native';
import React, { useCallback } from 'react';
import { View, Text, StyleSheet, Image,BackHandler } from 'react-native';
import { TouchableOpacity } from 'react-native-gesture-handler';
import { useRouter } from 'expo-router';
import { BellRing } from 'lucide-react-native';
import COLORS from '@/app/constants/colors';
import COLORS from '@/constants/colors';
import { useFocusEffect } from '@react-navigation/native';
const UserHomeScreen = () => {
const router = useRouter();
// Empêcher retour arrière
useFocusEffect(
useCallback(() => {
const onBackPress = () => true;
BackHandler.addEventListener('hardwareBackPress', onBackPress);
return () => BackHandler.removeEventListener('hardwareBackPress', onBackPress);
}, [])
);
return (
<View style={styles.container}>
@ -130,11 +141,3 @@ const styles = StyleSheet.create({
export default UserHomeScreen;
/*
<Button
title="Back to Opening Screen"
onPress={() => router.back()}
/>
*/

View File

@ -1,6 +1,6 @@
import { Tabs } from 'expo-router';
import { Home, Coffee, User } from 'lucide-react-native';
import { View, TouchableOpacity, Text, StyleSheet } from 'react-native';
import { StyleSheet } from 'react-native';
export default function UserLayout() {
return (

View File

@ -1,13 +1,12 @@
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,
import COLORS from "@/constants/colors";
import { CartItem as CartItemType } from '@/constants/types';
import QuantityControl from '@/components/QuantityControl';
import Animated, {
useAnimatedStyle,
withTiming,
useSharedValue,
runOnJS
useSharedValue
} from 'react-native-reanimated';
interface CartItemProps {
@ -18,13 +17,11 @@ interface CartItemProps {
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 animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
transform: [{ scale: scale.value }],
}));
const handleIncrease = () => {
scale.value = withTiming(1.02, { duration: 100 }, () => {
@ -58,7 +55,7 @@ const CartItem: React.FC<CartItemProps> = ({ item, onUpdateQuantity }) => {
onDecrease={handleDecrease}
/>
<Text style={styles.price}>{item.price}{item.priceUnit}</Text>
<Text style={styles.price}>{item.price}TND/Kg</Text>
</View>
</View>
</Animated.View>
@ -70,15 +67,23 @@ const styles = StyleSheet.create({
flexDirection: 'row',
backgroundColor: COLORS.secondary,
borderRadius: 12,
marginBottom: 5,
padding: 4,
marginBottom: 16,
padding: 16,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.2,
shadowRadius: 4,
elevation: 5,
},
imageContainer: {
width: 80,
height: 80,
borderRadius: 8,
overflow: 'hidden',
marginRight: 2,
marginRight: 16,
},
image: {
width: '100%',
@ -90,8 +95,9 @@ const styles = StyleSheet.create({
},
name: {
fontSize: 16,
color: COLORS.text,
marginBottom: 5,
fontWeight: 700,
color: '#000',
marginBottom: 8,
},
controlRow: {
flexDirection: 'row',
@ -100,7 +106,7 @@ const styles = StyleSheet.create({
},
price: {
fontSize: 14,
color: COLORS.secondary,
color: COLORS.primary,
},
});

View File

@ -1,7 +1,7 @@
import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native';
import { useRouter } from 'expo-router';
import { ChevronRight } from 'lucide-react-native';
import COLORS from '@/app/constants/colors';
import COLORS from '@/constants/colors';
import { Product } from '../constants/types';
interface ProductCardProps {

View File

@ -1,8 +1,7 @@
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 Animated, { useAnimatedStyle, withTiming, withSequence } from 'react-native-reanimated';
import * as Haptics from 'expo-haptics';
interface QuantityControlProps {
@ -45,7 +44,7 @@ const QuantityControl: React.FC<QuantityControlProps> = ({
onPress={handleDecrease}
disabled={quantity <= 0}
>
<Minus size={16} color={COLORS.text} />
<Minus size={16} color={'#333333'} />
</TouchableOpacity>
<Animated.Text style={[styles.quantity, animatedTextStyle]}>
@ -53,7 +52,7 @@ const QuantityControl: React.FC<QuantityControlProps> = ({
</Animated.Text>
<TouchableOpacity style={styles.button} onPress={handleIncrease}>
<Plus size={16} color={COLORS.text} />
<Plus size={16} color={'#333333'} />
</TouchableOpacity>
</View>
);
@ -69,18 +68,18 @@ const styles = StyleSheet.create({
button: {
width: 36,
height: 36,
backgroundColor: COLORS.secondary,
backgroundColor: '#E6D0B3',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 6,
},
quantity: {
fontSize: 14,
color: COLORS.text,
paddingHorizontal: 6,
color: '#333333',
paddingHorizontal: 16,
minWidth: 40,
textAlign: 'center',
},
});
export default QuantityControl;
export default QuantityControl;

View File

@ -10,7 +10,6 @@ export interface Product {
id: string;
name: string;
price: number;
priceUnit: string;
quantity: number;
image: string;
}

View File

@ -1,9 +1,11 @@
// context/CartContext.tsx
import React, { createContext, useState, useContext, ReactNode } from 'react';
type CartItem = {
productName: string;
export type CartItem = {
id: string;
name: string;
quantity: number;
price: number;
image: string;
};
type CartState = {
@ -15,6 +17,7 @@ type CartContextType = {
cart: CartState;
addItem: (item: CartItem) => void;
clearCart: () => void;
updateQuantity: (id: string, quantity: number) => void;
};
const CartContext = createContext<CartContextType | undefined>(undefined);
@ -24,36 +27,45 @@ export const CartProvider = ({ children }: { children: ReactNode }) => {
const addItem = (item: CartItem) => {
setCart(prev => {
const existing = prev.items.find(i => i.productName === item.productName);
const existing = prev.items.find(i => i.id === item.id);
let updatedItems;
if (existing) {
updatedItems = prev.items.map(i =>
i.productName === item.productName
? { ...i, quantity: i.quantity + item.quantity }
: i
i.id === item.id ? { ...i, quantity: i.quantity + item.quantity } : i
);
} else {
updatedItems = [...prev.items, item];
}
const updatedTotal = updatedItems.reduce((sum, i) => sum + i.quantity, 0);
const updatedTotal = updatedItems.reduce((sum, i) => sum + i.quantity * i.price, 0);
return { items: updatedItems, total: updatedTotal };
});
};
const clearCart = () => setCart({ items: [], total: 0 });
const updateQuantity = (id: string, quantity: number) => {
setCart(prev => {
const updatedItems = prev.items.map(item =>
item.id === id ? { ...item, quantity } : item
);
const updatedTotal = updatedItems.reduce((sum, i) => sum + i.quantity * i.price, 0);
return { items: updatedItems, total: updatedTotal };
});
};
return (
<CartContext.Provider value={{ cart, addItem, clearCart }}>
<CartContext.Provider value={{ cart, addItem, clearCart, updateQuantity }}>
{children}
</CartContext.Provider>
);
};
export const useCart = () => {
const context = useContext(CartContext);
if (!context) {
throw new Error('useCart must be used within a CartProvider');
}
return context;
};
const context = useContext(CartContext);
if (!context) {
throw new Error('useCart must be used within a CartProvider');
}
return context;
};

View File

@ -1,9 +1,6 @@
import { getAuth, signInWithEmailAndPassword, createUserWithEmailAndPassword } from "firebase/auth";
import { app } from "./config"; // ton fichier config Firebase
const auth = getAuth(app);
// auth.ts
import { signInWithEmailAndPassword, createUserWithEmailAndPassword } from "firebase/auth";
import { auth } from "./config";
export const signIn = async (email: string, password: string) => {
try {
@ -14,7 +11,6 @@ export const signIn = async (email: string, password: string) => {
}
};
export const signUp = async (email: string, password: string) => {
try {
const userCredential = await createUserWithEmailAndPassword(auth, email, password);

View File

@ -1,8 +1,11 @@
// firebase/config.ts
import { FirebaseApp, initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
import { getAuth, initializeAuth } from 'firebase/auth';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as firebaseAuth from 'firebase/auth';
const reactNativePersistence = (firebaseAuth as any).getReactNativePersistence;
// Configuration Firebase
const firebaseConfig = {
apiKey: 'AIzaSyDHoF8Eahk60s3APh7WxohL1bya_44v39k',
authDomain: 'brix-cafe-2ddf1.firebaseapp.com',
@ -15,5 +18,13 @@ const firebaseConfig = {
const app: FirebaseApp = initializeApp(firebaseConfig);
export const db = getFirestore(app);
export { app };
// Initialiser Auth avec persistance
const auth = initializeAuth(app, {
persistence: reactNativePersistence(AsyncStorage),
});
// Initialiser Firestore
const db = getFirestore(app);
export { app, db, auth };

View File

@ -1,9 +1,6 @@
// firebase/session.ts
import { getAuth, onAuthStateChanged, User } from "firebase/auth";
import { onAuthStateChanged, User } from "firebase/auth";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { app } from "./config";
const auth = getAuth(app);
import { auth } from "./config";
export const monitorAuthState = (onUserChange: (user: User | null) => void) => {
onAuthStateChanged(auth, async (user) => {
@ -16,8 +13,3 @@ export const monitorAuthState = (onUserChange: (user: User | null) => void) => {
}
});
};
export const getStoredUser = async (): Promise<User | null> => {
const json = await AsyncStorage.getItem("user");
return json ? JSON.parse(json) : null;
};

2
package-lock.json generated
View File

@ -9,7 +9,7 @@
"version": "1.0.0",
"dependencies": {
"@expo/vector-icons": "^14.0.2",
"@react-native-async-storage/async-storage": "1.23.1",
"@react-native-async-storage/async-storage": "^1.23.1",
"@react-navigation/bottom-tabs": "^7.2.0",
"@react-navigation/native": "^7.0.14",
"expo": "~52.0.46",

View File

@ -16,7 +16,7 @@
},
"dependencies": {
"@expo/vector-icons": "^14.0.2",
"@react-native-async-storage/async-storage": "1.23.1",
"@react-native-async-storage/async-storage": "^1.23.1",
"@react-navigation/bottom-tabs": "^7.2.0",
"@react-navigation/native": "^7.0.14",
"expo": "~52.0.46",

View File

@ -1,4 +1,4 @@
import { CartItem } from '@/app/constants/types';
import { CartItem } from '@/constants/types';
export const calculateTotal = (items: CartItem[]): number => {
return items.reduce((total, item) => {