Cart Logic v2
This commit is contained in:
parent
befa166ef5
commit
eb23fd4696
@ -3,7 +3,7 @@ import { Stack } from 'expo-router';
|
|||||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { CartProvider } from './context/cartContext'
|
import { CartProvider } from '../context/cartContext'
|
||||||
|
|
||||||
export default function Layout() {
|
export default function Layout() {
|
||||||
return (
|
return (
|
||||||
|
112
app/index.tsx
112
app/index.tsx
@ -1,90 +1,38 @@
|
|||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native';
|
import { View, ActivityIndicator } from 'react-native';
|
||||||
|
import { getAuth, onAuthStateChanged } from 'firebase/auth';
|
||||||
import { router } from 'expo-router';
|
import { router } from 'expo-router';
|
||||||
import COLORS from './constants/colors';
|
import OpeningScreen from './screens/auth/OpeningScreen'; // ajuste le chemin si besoin
|
||||||
import { StatusBar } from 'expo-status-bar';
|
|
||||||
|
|
||||||
|
const Index = () => {
|
||||||
|
const [checkingAuth, setCheckingAuth] = useState(true);
|
||||||
|
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||||
|
|
||||||
const OpeningScreen = () => {
|
useEffect(() => {
|
||||||
|
const auth = getAuth();
|
||||||
|
|
||||||
return (
|
const unsubscribe = onAuthStateChanged(auth, (user) => {
|
||||||
<View style={styles.container}>
|
if (user) {
|
||||||
<StatusBar style="dark" />
|
setIsLoggedIn(true);
|
||||||
<Image source={require('../assets/images/logo.png')} style={styles.logo} />
|
router.replace('/screens/user/UserHomeScreen'); // redirige vers Home
|
||||||
<Text style={styles.welcomeText}>Bienvenue chez Brix Café</Text>
|
} else {
|
||||||
<Image source={require('../assets/images/coffee_cup.jpg')} style={styles.coffeeImage} />
|
setIsLoggedIn(false); // utilisateur non connecté
|
||||||
<Text style={styles.descriptionText}>
|
}
|
||||||
Depuis 2024, Brix Café vous fait vivre une expérience café unique,
|
setCheckingAuth(false); // vérification terminée
|
||||||
inspirée du savoir-faire italien et portée par une passion authentique.
|
});
|
||||||
Des grains d’exception, une qualité incomparable.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<TouchableOpacity style={styles.signInButton} onPress={() => router.push('/screens/auth/SignIn-Screen')}>
|
return () => unsubscribe();
|
||||||
<Text style={styles.buttonText}>Se connecter</Text>
|
}, []);
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
<TouchableOpacity style={styles.signUpButton} onPress={() => router.push('/screens/auth/SignUpScreen')}>
|
if (checkingAuth) {
|
||||||
<Text style={styles.buttonText}>Créer un compte</Text>
|
return (
|
||||||
</TouchableOpacity>
|
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#000' }}>
|
||||||
</View>
|
<ActivityIndicator size="large" color="#fff" />
|
||||||
);
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !isLoggedIn ? <OpeningScreen /> : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Styles
|
export default Index;
|
||||||
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;
|
|
||||||
|
90
app/screens/auth/OpeningScreen.tsx
Normal file
90
app/screens/auth/OpeningScreen.tsx
Normal 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 d’exception, 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;
|
@ -5,8 +5,8 @@ import { router } from "expo-router";
|
|||||||
import { signIn } from "../../../firebase/auth"; // Assure-toi que le chemin est correct
|
import { signIn } from "../../../firebase/auth"; // Assure-toi que le chemin est correct
|
||||||
import { Link } from "expo-router";
|
import { Link } from "expo-router";
|
||||||
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view"; // Import the library
|
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 = () => {
|
const SignInScreen = () => {
|
||||||
@ -45,11 +45,19 @@ const SignInScreen = () => {
|
|||||||
if (!validateForm()) return;
|
if (!validateForm()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { user } = await signIn(form.email, form.password); // Destructure to get user
|
const { user } = await signIn(form.email, form.password);
|
||||||
console.log("Connexion réussie :", user.email); // Access the email directly
|
|
||||||
|
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");
|
router.replace("/screens/user/UserHomeScreen");
|
||||||
} catch (error: any) {
|
} 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" />
|
<ArrowLeft size={24} color="#666" />
|
||||||
</TouchableOpacity>
|
</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>
|
<Text style={styles.title}>Se connecter</Text>
|
||||||
|
|
||||||
@ -117,9 +125,9 @@ const SignInScreen = () => {
|
|||||||
style={styles.eyeIcon}
|
style={styles.eyeIcon}
|
||||||
>
|
>
|
||||||
{showPassword ? (
|
{showPassword ? (
|
||||||
<EyeOff size={20} color="#666" />
|
|
||||||
) : (
|
|
||||||
<Eye size={20} color="#666" />
|
<Eye size={20} color="#666" />
|
||||||
|
) : (
|
||||||
|
<EyeOff size={20} color="#666" />
|
||||||
)}
|
)}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
|
@ -1,131 +1,238 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import { useRouter } from 'expo-router';
|
import {
|
||||||
import { View, Text, Button, Alert, StyleSheet } from 'react-native';
|
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 { getAuth } from 'firebase/auth';
|
||||||
import { collection, addDoc } from 'firebase/firestore'; // Make sure these are imported
|
import { collection, addDoc } from 'firebase/firestore';
|
||||||
import { db } from '@/firebase/config'; // Ensure this path is correct
|
import { db } from '@/firebase/config';
|
||||||
import { useCart } from '@/app/context/cartContext';
|
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 = () => {
|
export default function CartScreen() {
|
||||||
const { cart, clearCart } = useCart();
|
const { cart, clearCart, updateQuantity } = useCart();
|
||||||
|
const totalAmount = cart.total;
|
||||||
|
const [isProcessingOrder, setIsProcessingOrder] = useState(false);
|
||||||
const auth = getAuth();
|
const auth = getAuth();
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// Check if the user is logged in when the component mounts
|
const totalValue = useSharedValue(1);
|
||||||
useEffect(() => {
|
const buttonScale = useSharedValue(1);
|
||||||
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) {
|
|
||||||
|
const totalAnimatedStyle = useAnimatedStyle(() => ({
|
||||||
|
transform: [{ scale: totalValue.value }],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const handleConfirmOrder = useCallback(async () => {
|
||||||
|
|
||||||
|
|
||||||
|
if (totalAmount <= 0) {
|
||||||
Alert.alert('Panier vide', 'Veuillez ajouter des produits à votre panier');
|
Alert.alert('Panier vide', 'Veuillez ajouter des produits à votre panier');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
setIsProcessingOrder(true);
|
||||||
const userId = auth.currentUser?.uid;
|
buttonScale.value = withSequence(
|
||||||
if (!userId) {
|
withTiming(0.95, { duration: 100 }),
|
||||||
Alert.alert('Erreur', 'Utilisateur non connecté.');
|
withTiming(1, { duration: 150 })
|
||||||
return;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
|
if (Platform.OS !== 'web') {
|
||||||
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const orderData: any = {
|
const orderData: any = {
|
||||||
userId,
|
|
||||||
status: 'En attente',
|
status: 'En attente',
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
totalAmount: totalAmount
|
||||||
};
|
};
|
||||||
|
|
||||||
cart.items.forEach((item, index) => {
|
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);
|
await addDoc(collection(db, 'orders'), orderData);
|
||||||
|
Alert.alert('Commande confirmée', `Montant Total : ${totalAmount}DT`);
|
||||||
Alert.alert('Commande confirmée', 'Votre commande a été enregistrée.');
|
|
||||||
clearCart();
|
clearCart();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur de commande', error);
|
console.error('Erreur commande :', error);
|
||||||
Alert.alert('Erreur', "Impossible d'enregistrer la commande.");
|
Alert.alert('Erreur', "Impossible d'enregistrer la commande.");
|
||||||
|
} finally {
|
||||||
|
setIsProcessingOrder(false);
|
||||||
}
|
}
|
||||||
};
|
}, [cart, totalAmount]);
|
||||||
|
|
||||||
if (!isAuthenticated) {
|
const renderItem = useCallback(({ item }: { item: CartItemType }) => (
|
||||||
return <Text>Chargement...</Text>; // Loading state while checking authentication
|
<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 (
|
return (
|
||||||
<View style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
<Text style={styles.title}>Votre Panier</Text>
|
<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 ? (
|
<View style={styles.content}>
|
||||||
<Text style={styles.empty}>Votre panier est vide.</Text>
|
<FlatList
|
||||||
) : (
|
data={cart.items}
|
||||||
cart.items.map((item, index) => (
|
renderItem={renderItem}
|
||||||
<View key={index} style={styles.itemContainer}>
|
keyExtractor={keyExtractor}
|
||||||
<Text style={styles.itemText}>
|
showsVerticalScrollIndicator={false}
|
||||||
{item.productName} - Quantité : {item.quantity}
|
ListEmptyComponent={
|
||||||
</Text>
|
<View style={styles.emptyContainer}>
|
||||||
</View>
|
<Text style={styles.emptyText}>Votre panier est vide</Text>
|
||||||
))
|
</View>
|
||||||
)}
|
}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
{cart.items.length > 0 && (
|
<View style={styles.footer}>
|
||||||
<View style={styles.buttonContainer}>
|
<View style={styles.totalContainer}>
|
||||||
<Button title="Confirmer la commande" onPress={handleConfirmOrder} color="#8B4513" />
|
<Text style={styles.totalLabel}>Total : </Text>
|
||||||
<View style={{ marginTop: 10 }}>
|
<Animated.Text style={[styles.totalAmount, totalAnimatedStyle]}>
|
||||||
<Button title="Vider le panier" onPress={clearCart} color="#A52A2A" />
|
{totalAmount}DT
|
||||||
</View>
|
</Animated.Text>
|
||||||
</View>
|
</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({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: { flex: 1, backgroundColor: '#fff' },
|
||||||
padding: 20,
|
header: {
|
||||||
backgroundColor: '#FFF',
|
height: 60,
|
||||||
flexGrow: 1,
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
position: 'relative',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#eee',
|
||||||
|
marginTop: 5,
|
||||||
},
|
},
|
||||||
title: {
|
backButton: {
|
||||||
fontSize: 22,
|
position: 'absolute',
|
||||||
fontWeight: 'bold',
|
left: 20,
|
||||||
marginBottom: 15,
|
top: '50%',
|
||||||
textAlign: 'center',
|
transform: [{ translateY: -12 }],
|
||||||
color: '#5A3E36',
|
|
||||||
},
|
},
|
||||||
empty: {
|
headerTitle: {
|
||||||
fontSize: 16,
|
fontSize: 20,
|
||||||
color: '#777',
|
fontWeight: '600',
|
||||||
textAlign: 'center',
|
|
||||||
marginTop: 50,
|
|
||||||
},
|
|
||||||
itemContainer: {
|
|
||||||
backgroundColor: '#F5F5DC',
|
|
||||||
padding: 15,
|
|
||||||
marginBottom: 10,
|
|
||||||
borderRadius: 10,
|
|
||||||
borderColor: '#DDD',
|
|
||||||
borderWidth: 1,
|
|
||||||
},
|
|
||||||
itemText: {
|
|
||||||
fontSize: 16,
|
|
||||||
color: '#333',
|
color: '#333',
|
||||||
},
|
},
|
||||||
buttonContainer: {
|
content: {
|
||||||
marginTop: 30,
|
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;
|
|
||||||
|
@ -2,12 +2,12 @@ import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-nati
|
|||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { ArrowLeft } from 'lucide-react-native';
|
import { ArrowLeft } from 'lucide-react-native';
|
||||||
import { router } from "expo-router";
|
import { router } from "expo-router";
|
||||||
import ProductCard from '../../components/ProductCard';
|
import ProductCard from '@/components/ProductCard';
|
||||||
import COLORS from '@/app/constants/colors';
|
import COLORS from '@/constants/colors';
|
||||||
import { collection, getDocs } from 'firebase/firestore';
|
import { collection, getDocs } from 'firebase/firestore';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { db } from '@/firebase/config';
|
import { db } from '@/firebase/config';
|
||||||
import { Product } from '@/app/constants/types';
|
import { Product } from '@/constants/types';
|
||||||
|
|
||||||
export default function GrainsScreen() {
|
export default function GrainsScreen() {
|
||||||
const [products, setProducts] = useState<Product[]>([]);
|
const [products, setProducts] = useState<Product[]>([]);
|
||||||
@ -74,7 +74,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
padding: 28,
|
padding: 30,
|
||||||
backgroundColor: COLORS.background_user,
|
backgroundColor: COLORS.background_user,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -5,8 +5,8 @@ import { useLocalSearchParams, useRouter } from 'expo-router';
|
|||||||
import { ChevronLeft, Minus, Plus } from 'lucide-react-native';
|
import { ChevronLeft, Minus, Plus } from 'lucide-react-native';
|
||||||
import { doc, getDoc } from 'firebase/firestore';
|
import { doc, getDoc } from 'firebase/firestore';
|
||||||
import { db } from '@/firebase/config';
|
import { db } from '@/firebase/config';
|
||||||
import { useCart } from '@/app/context/cartContext';
|
import { useCart } from '@/context/cartContext';
|
||||||
import { Product } from '@/app/constants/types';
|
import { Product } from '@/constants/types';
|
||||||
|
|
||||||
export default function ProductScreen() {
|
export default function ProductScreen() {
|
||||||
const { productId } = useLocalSearchParams();
|
const { productId } = useLocalSearchParams();
|
||||||
@ -55,12 +55,18 @@ export default function ProductScreen() {
|
|||||||
const { addItem } = useCart();
|
const { addItem } = useCart();
|
||||||
|
|
||||||
const handleAddToCart = () => {
|
const handleAddToCart = () => {
|
||||||
if (!product) return;
|
if (!product) return;
|
||||||
|
|
||||||
|
addItem({
|
||||||
|
id: product.id,
|
||||||
|
name: product.productName,
|
||||||
|
quantity: quantity,
|
||||||
|
price: parseFloat(product.price), // Assure-toi que price est bien un number
|
||||||
|
image: product.image,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
addItem({
|
|
||||||
productName: product?.productName,
|
|
||||||
quantity: quantity,
|
|
||||||
});
|
|
||||||
|
|
||||||
Alert.alert('Ajouté', `${quantity} ${product.productName} ajouté au panier`);
|
Alert.alert('Ajouté', `${quantity} ${product.productName} ajouté au panier`);
|
||||||
};
|
};
|
||||||
@ -70,7 +76,7 @@ const handleAddToCart = () => {
|
|||||||
<SafeAreaView style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<TouchableOpacity onPress={() => router.back()}>
|
<TouchableOpacity onPress={() => router.push('/screens/user/GrainsScreen')}>
|
||||||
<ChevronLeft size={24} color="#333" />
|
<ChevronLeft size={24} color="#333" />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Text style={styles.title}>{product?.productName || 'Détails du produit'}</Text>
|
<Text style={styles.title}>{product?.productName || 'Détails du produit'}</Text>
|
||||||
|
@ -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 { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { ArrowLeft } from 'lucide-react-native';
|
import { ArrowLeft } from 'lucide-react-native';
|
||||||
import { router } from "expo-router";
|
import { router } from "expo-router";
|
||||||
|
import { getAuth, signOut } from "firebase/auth";
|
||||||
|
|
||||||
export default function ProfileScreen() {
|
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 (
|
return (
|
||||||
<SafeAreaView style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<TouchableOpacity style={styles.backButton} onPress={() => router.back()}>
|
<TouchableOpacity style={styles.backButton} onPress={() => router.back()}>
|
||||||
<ArrowLeft size={24} color="#666" />
|
<ArrowLeft size={24} color="#666" />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Text style={styles.headerTitle}>Espace Personel</Text>
|
<Text style={styles.headerTitle}>Espace Personnel</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.body}>
|
||||||
|
<TouchableOpacity style={styles.signOutButton} onPress={handleSignOut}>
|
||||||
|
<Text style={styles.signOutButtonText}>Se déconnecter</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -30,7 +60,7 @@ const styles = StyleSheet.create({
|
|||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: '#eee',
|
borderBottomColor: '#eee',
|
||||||
marginTop:5,
|
marginTop: 5,
|
||||||
},
|
},
|
||||||
backButton: {
|
backButton: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@ -43,4 +73,20 @@ const styles = StyleSheet.create({
|
|||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: '#333',
|
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',
|
||||||
|
},
|
||||||
});
|
});
|
@ -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 { TouchableOpacity } from 'react-native-gesture-handler';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
import { BellRing } from 'lucide-react-native';
|
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 UserHomeScreen = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
// Empêcher retour arrière
|
||||||
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
|
const onBackPress = () => true;
|
||||||
|
BackHandler.addEventListener('hardwareBackPress', onBackPress);
|
||||||
|
return () => BackHandler.removeEventListener('hardwareBackPress', onBackPress);
|
||||||
|
}, [])
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
@ -130,11 +141,3 @@ const styles = StyleSheet.create({
|
|||||||
export default UserHomeScreen;
|
export default UserHomeScreen;
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
<Button
|
|
||||||
title="Back to Opening Screen"
|
|
||||||
onPress={() => router.back()}
|
|
||||||
/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Tabs } from 'expo-router';
|
import { Tabs } from 'expo-router';
|
||||||
import { Home, Coffee, User } from 'lucide-react-native';
|
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() {
|
export default function UserLayout() {
|
||||||
return (
|
return (
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, StyleSheet, Image } from 'react-native';
|
import { View, Text, StyleSheet, Image } from 'react-native';
|
||||||
import COLORS from "@/app/constants/colors";
|
import COLORS from "@/constants/colors";
|
||||||
import { CartItem as CartItemType } from '@/app/constants/types';
|
import { CartItem as CartItemType } from '@/constants/types';
|
||||||
import QuantityControl from '@/app/components/QuantityControl';
|
import QuantityControl from '@/components/QuantityControl';
|
||||||
import Animated, {
|
import Animated, {
|
||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
withTiming,
|
withTiming,
|
||||||
useSharedValue,
|
useSharedValue
|
||||||
runOnJS
|
|
||||||
} from 'react-native-reanimated';
|
} from 'react-native-reanimated';
|
||||||
|
|
||||||
interface CartItemProps {
|
interface CartItemProps {
|
||||||
@ -19,12 +18,10 @@ const CartItem: React.FC<CartItemProps> = ({ item, onUpdateQuantity }) => {
|
|||||||
const opacity = useSharedValue(1);
|
const opacity = useSharedValue(1);
|
||||||
const scale = useSharedValue(1);
|
const scale = useSharedValue(1);
|
||||||
|
|
||||||
const animatedStyle = useAnimatedStyle(() => {
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
return {
|
opacity: opacity.value,
|
||||||
opacity: opacity.value,
|
transform: [{ scale: scale.value }],
|
||||||
transform: [{ scale: scale.value }]
|
}));
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleIncrease = () => {
|
const handleIncrease = () => {
|
||||||
scale.value = withTiming(1.02, { duration: 100 }, () => {
|
scale.value = withTiming(1.02, { duration: 100 }, () => {
|
||||||
@ -58,7 +55,7 @@ const CartItem: React.FC<CartItemProps> = ({ item, onUpdateQuantity }) => {
|
|||||||
onDecrease={handleDecrease}
|
onDecrease={handleDecrease}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Text style={styles.price}>{item.price}{item.priceUnit}</Text>
|
<Text style={styles.price}>{item.price}TND/Kg</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
@ -70,15 +67,23 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
backgroundColor: COLORS.secondary,
|
backgroundColor: COLORS.secondary,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
marginBottom: 5,
|
marginBottom: 16,
|
||||||
padding: 4,
|
padding: 16,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: {
|
||||||
|
width: 0,
|
||||||
|
height: 2,
|
||||||
|
},
|
||||||
|
shadowOpacity: 0.2,
|
||||||
|
shadowRadius: 4,
|
||||||
|
elevation: 5,
|
||||||
},
|
},
|
||||||
imageContainer: {
|
imageContainer: {
|
||||||
width: 80,
|
width: 80,
|
||||||
height: 80,
|
height: 80,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
marginRight: 2,
|
marginRight: 16,
|
||||||
},
|
},
|
||||||
image: {
|
image: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@ -90,8 +95,9 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: COLORS.text,
|
fontWeight: 700,
|
||||||
marginBottom: 5,
|
color: '#000',
|
||||||
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
controlRow: {
|
controlRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@ -100,7 +106,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
price: {
|
price: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: COLORS.secondary,
|
color: COLORS.primary,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native';
|
import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
import { ChevronRight } from 'lucide-react-native';
|
import { ChevronRight } from 'lucide-react-native';
|
||||||
import COLORS from '@/app/constants/colors';
|
import COLORS from '@/constants/colors';
|
||||||
import { Product } from '../constants/types';
|
import { Product } from '../constants/types';
|
||||||
|
|
||||||
interface ProductCardProps {
|
interface ProductCardProps {
|
@ -1,8 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, TouchableOpacity, StyleSheet, Platform } from 'react-native';
|
import { View, Text, TouchableOpacity, StyleSheet, Platform } from 'react-native';
|
||||||
import COLORS from "@/app/constants/colors";
|
|
||||||
import { Minus, Plus } from 'lucide-react-native';
|
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';
|
import * as Haptics from 'expo-haptics';
|
||||||
|
|
||||||
interface QuantityControlProps {
|
interface QuantityControlProps {
|
||||||
@ -45,7 +44,7 @@ const QuantityControl: React.FC<QuantityControlProps> = ({
|
|||||||
onPress={handleDecrease}
|
onPress={handleDecrease}
|
||||||
disabled={quantity <= 0}
|
disabled={quantity <= 0}
|
||||||
>
|
>
|
||||||
<Minus size={16} color={COLORS.text} />
|
<Minus size={16} color={'#333333'} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<Animated.Text style={[styles.quantity, animatedTextStyle]}>
|
<Animated.Text style={[styles.quantity, animatedTextStyle]}>
|
||||||
@ -53,7 +52,7 @@ const QuantityControl: React.FC<QuantityControlProps> = ({
|
|||||||
</Animated.Text>
|
</Animated.Text>
|
||||||
|
|
||||||
<TouchableOpacity style={styles.button} onPress={handleIncrease}>
|
<TouchableOpacity style={styles.button} onPress={handleIncrease}>
|
||||||
<Plus size={16} color={COLORS.text} />
|
<Plus size={16} color={'#333333'} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@ -69,15 +68,15 @@ const styles = StyleSheet.create({
|
|||||||
button: {
|
button: {
|
||||||
width: 36,
|
width: 36,
|
||||||
height: 36,
|
height: 36,
|
||||||
backgroundColor: COLORS.secondary,
|
backgroundColor: '#E6D0B3',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
},
|
},
|
||||||
quantity: {
|
quantity: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: COLORS.text,
|
color: '#333333',
|
||||||
paddingHorizontal: 6,
|
paddingHorizontal: 16,
|
||||||
minWidth: 40,
|
minWidth: 40,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
},
|
},
|
@ -10,7 +10,6 @@ export interface Product {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
price: number;
|
price: number;
|
||||||
priceUnit: string;
|
|
||||||
quantity: number;
|
quantity: number;
|
||||||
image: string;
|
image: string;
|
||||||
}
|
}
|
@ -1,9 +1,11 @@
|
|||||||
// context/CartContext.tsx
|
|
||||||
import React, { createContext, useState, useContext, ReactNode } from 'react';
|
import React, { createContext, useState, useContext, ReactNode } from 'react';
|
||||||
|
|
||||||
type CartItem = {
|
export type CartItem = {
|
||||||
productName: string;
|
id: string;
|
||||||
|
name: string;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
|
price: number;
|
||||||
|
image: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CartState = {
|
type CartState = {
|
||||||
@ -15,6 +17,7 @@ type CartContextType = {
|
|||||||
cart: CartState;
|
cart: CartState;
|
||||||
addItem: (item: CartItem) => void;
|
addItem: (item: CartItem) => void;
|
||||||
clearCart: () => void;
|
clearCart: () => void;
|
||||||
|
updateQuantity: (id: string, quantity: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CartContext = createContext<CartContextType | undefined>(undefined);
|
const CartContext = createContext<CartContextType | undefined>(undefined);
|
||||||
@ -24,36 +27,45 @@ export const CartProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
|
|
||||||
const addItem = (item: CartItem) => {
|
const addItem = (item: CartItem) => {
|
||||||
setCart(prev => {
|
setCart(prev => {
|
||||||
const existing = prev.items.find(i => i.productName === item.productName);
|
const existing = prev.items.find(i => i.id === item.id);
|
||||||
let updatedItems;
|
let updatedItems;
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
updatedItems = prev.items.map(i =>
|
updatedItems = prev.items.map(i =>
|
||||||
i.productName === item.productName
|
i.id === item.id ? { ...i, quantity: i.quantity + item.quantity } : i
|
||||||
? { ...i, quantity: i.quantity + item.quantity }
|
|
||||||
: i
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
updatedItems = [...prev.items, item];
|
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 };
|
return { items: updatedItems, total: updatedTotal };
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearCart = () => setCart({ items: [], total: 0 });
|
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 (
|
return (
|
||||||
<CartContext.Provider value={{ cart, addItem, clearCart }}>
|
<CartContext.Provider value={{ cart, addItem, clearCart, updateQuantity }}>
|
||||||
{children}
|
{children}
|
||||||
</CartContext.Provider>
|
</CartContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCart = () => {
|
export const useCart = () => {
|
||||||
const context = useContext(CartContext);
|
const context = useContext(CartContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error('useCart must be used within a CartProvider');
|
throw new Error('useCart must be used within a CartProvider');
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
@ -1,9 +1,6 @@
|
|||||||
import { getAuth, signInWithEmailAndPassword, createUserWithEmailAndPassword } from "firebase/auth";
|
// auth.ts
|
||||||
import { app } from "./config"; // ton fichier config Firebase
|
import { signInWithEmailAndPassword, createUserWithEmailAndPassword } from "firebase/auth";
|
||||||
|
import { auth } from "./config";
|
||||||
|
|
||||||
|
|
||||||
const auth = getAuth(app);
|
|
||||||
|
|
||||||
export const signIn = async (email: string, password: string) => {
|
export const signIn = async (email: string, password: string) => {
|
||||||
try {
|
try {
|
||||||
@ -14,7 +11,6 @@ export const signIn = async (email: string, password: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const signUp = async (email: string, password: string) => {
|
export const signUp = async (email: string, password: string) => {
|
||||||
try {
|
try {
|
||||||
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
|
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
// firebase/config.ts
|
|
||||||
import { FirebaseApp, initializeApp } from 'firebase/app';
|
import { FirebaseApp, initializeApp } from 'firebase/app';
|
||||||
import { getFirestore } from 'firebase/firestore';
|
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 = {
|
const firebaseConfig = {
|
||||||
apiKey: 'AIzaSyDHoF8Eahk60s3APh7WxohL1bya_44v39k',
|
apiKey: 'AIzaSyDHoF8Eahk60s3APh7WxohL1bya_44v39k',
|
||||||
authDomain: 'brix-cafe-2ddf1.firebaseapp.com',
|
authDomain: 'brix-cafe-2ddf1.firebaseapp.com',
|
||||||
@ -15,5 +18,13 @@ const firebaseConfig = {
|
|||||||
|
|
||||||
const app: FirebaseApp = initializeApp(firebaseConfig);
|
const app: FirebaseApp = initializeApp(firebaseConfig);
|
||||||
|
|
||||||
export const db = getFirestore(app);
|
// Initialiser Auth avec persistance
|
||||||
export { app };
|
const auth = initializeAuth(app, {
|
||||||
|
persistence: reactNativePersistence(AsyncStorage),
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Initialiser Firestore
|
||||||
|
const db = getFirestore(app);
|
||||||
|
|
||||||
|
export { app, db, auth };
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
// firebase/session.ts
|
import { onAuthStateChanged, User } from "firebase/auth";
|
||||||
import { getAuth, onAuthStateChanged, User } from "firebase/auth";
|
|
||||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
import { app } from "./config";
|
import { auth } from "./config";
|
||||||
|
|
||||||
const auth = getAuth(app);
|
|
||||||
|
|
||||||
export const monitorAuthState = (onUserChange: (user: User | null) => void) => {
|
export const monitorAuthState = (onUserChange: (user: User | null) => void) => {
|
||||||
onAuthStateChanged(auth, async (user) => {
|
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
2
package-lock.json
generated
@ -9,7 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^14.0.2",
|
"@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/bottom-tabs": "^7.2.0",
|
||||||
"@react-navigation/native": "^7.0.14",
|
"@react-navigation/native": "^7.0.14",
|
||||||
"expo": "~52.0.46",
|
"expo": "~52.0.46",
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^14.0.2",
|
"@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/bottom-tabs": "^7.2.0",
|
||||||
"@react-navigation/native": "^7.0.14",
|
"@react-navigation/native": "^7.0.14",
|
||||||
"expo": "~52.0.46",
|
"expo": "~52.0.46",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CartItem } from '@/app/constants/types';
|
import { CartItem } from '@/constants/types';
|
||||||
|
|
||||||
export const calculateTotal = (items: CartItem[]): number => {
|
export const calculateTotal = (items: CartItem[]): number => {
|
||||||
return items.reduce((total, item) => {
|
return items.reduce((total, item) => {
|
Loading…
Reference in New Issue
Block a user