diff --git a/app/screens/auth/SignIn-Screen.tsx b/app/screens/auth/SignIn-Screen.tsx
index 60542a4..f1d970a 100644
--- a/app/screens/auth/SignIn-Screen.tsx
+++ b/app/screens/auth/SignIn-Screen.tsx
@@ -1,5 +1,5 @@
import { useState } from "react";
-import {View,Text,TextInput,Pressable,StyleSheet,Image,Alert,TouchableOpacity,} from "react-native";
+import {View,Text,TextInput,Pressable,StyleSheet,Image,Alert,TouchableOpacity,ActivityIndicator} from "react-native";
import { Eye, EyeOff,ArrowLeft,Check } from "lucide-react-native";
import { router } from "expo-router";
import { signIn } from "../../../firebase/auth"; // Assure-toi que le chemin est correct
@@ -10,6 +10,7 @@ import AsyncStorage from "@react-native-async-storage/async-storage";
const SignInScreen = () => {
+ const [loading, setLoading] = useState(false);
const [form, setForm] = useState({
email: "",
password: "",
@@ -43,7 +44,7 @@ const SignInScreen = () => {
const handleLogin = async () => {
if (!validateForm()) return;
-
+ setLoading(true);
try {
const { user } = await signIn(form.email, form.password);
@@ -57,7 +58,9 @@ const SignInScreen = () => {
router.replace("/screens/user/UserHomeScreen");
} catch (error: any) {
- Alert.alert("Erreur", error.message);
+ Alert.alert("Erreur", "Utilisateur Introuvable");
+ } finally{
+ setLoading(false);
}
};
@@ -159,9 +162,17 @@ const SignInScreen = () => {
-
- Connexion
-
+
+ {loading ? (
+
+ ) : (
+ Connexion
+ )}
+
Nouveau ici?
diff --git a/app/screens/user/CartScreen.tsx b/app/screens/user/CartScreen.tsx
index ce22018..f3ddd88 100644
--- a/app/screens/user/CartScreen.tsx
+++ b/app/screens/user/CartScreen.tsx
@@ -7,12 +7,14 @@ import {
FlatList,
Platform,
TouchableOpacity,
- Button,
+ 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';
+import { query, orderBy, limit, getDocs } from 'firebase/firestore';
+
import { db } from '@/firebase/config';
import CartItem from '@/components/CartItem';
import { ArrowLeft } from 'lucide-react-native';
@@ -44,39 +46,61 @@ export default function CartScreen() {
}));
const handleConfirmOrder = useCallback(async () => {
-
-
if (totalAmount <= 0) {
Alert.alert('Panier vide', 'Veuillez ajouter des produits à votre panier');
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 user = auth.currentUser;
+ if (!user) {
+ Alert.alert('Utilisateur non connecté', 'Veuillez vous connecter pour passer une commande.');
+ return;
+ }
+
+ // Utilisation de query pour appliquer orderBy et limit
+ const orderRef = collection(db, 'orders');
+ const q = query(orderRef, orderBy('createdAt', 'desc'), limit(1));
+ const orderSnapshot = await getDocs(q);
+ let lastOrderId = 0;
+
+ if (!orderSnapshot.empty) {
+ const lastOrderDoc = orderSnapshot.docs[0].data();
+ lastOrderId = parseInt(lastOrderDoc.orderId.replace(/^0+/, '')) || 0; // Remove leading zeros
+ }
+
+ const newOrderId = String(lastOrderId + 1).padStart(3, '0'); // Generate the new order ID
+
const orderData: any = {
+ orderId: newOrderId, // Use the generated ID
+ userId: user.uid,
status: 'En attente',
createdAt: new Date().toISOString(),
- totalAmount: totalAmount
+ totalAmount: totalAmount,
};
-
+
+ // Add each item in the cart to the orderData
cart.items.forEach((item, index) => {
orderData[`item${index + 1}`] = {
name: item.name,
quantity: item.quantity,
};
});
-
+
+ // Add the order to Firestore
await addDoc(collection(db, 'orders'), orderData);
Alert.alert('Commande confirmée', `Montant Total : ${totalAmount}DT`);
+
clearCart();
} catch (error) {
console.error('Erreur commande :', error);
@@ -84,7 +108,8 @@ export default function CartScreen() {
} finally {
setIsProcessingOrder(false);
}
- }, [cart, totalAmount]);
+ }, [cart, totalAmount, auth]);
+
const renderItem = useCallback(({ item }: { item: CartItemType }) => (
+
+
+ router.back()}>
+
+
+ Historique d'achat
+
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#fff',
+ },
+ header: {
+ height: 60,
+ justifyContent: 'center',
+ alignItems: 'center',
+ position: 'relative',
+ backgroundColor: '#fff',
+ borderBottomWidth: 1,
+ borderBottomColor: '#eee',
+ marginTop: 5,
+ },
+ backButton: {
+ position: 'absolute',
+ left: 20,
+ top: '50%',
+ transform: [{ translateY: -12 }],
+ },
+ headerTitle: {
+ fontSize: 20,
+ fontWeight: '600',
+ color: '#333',
+ },
+ content: {
+ padding: 15,
+ backgroundColor: '#fff',
+ paddingBottom: 10,
+ },
+});
\ No newline at end of file
diff --git a/app/screens/user/ProfileScreen.tsx b/app/screens/user/ProfileScreen.tsx
index 786f741..e639547 100644
--- a/app/screens/user/ProfileScreen.tsx
+++ b/app/screens/user/ProfileScreen.tsx
@@ -1,49 +1,165 @@
-import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Alert } from 'react-native';
-import { SafeAreaView } from 'react-native-safe-area-context';
+import React, { useEffect, useState } from 'react';
+import { StyleSheet, View, Text, TouchableOpacity, ScrollView, Alert, ActivityIndicator } from 'react-native';
+import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
+import { Feather } from '@expo/vector-icons';
import { ArrowLeft } from 'lucide-react-native';
import { router } from "expo-router";
-import { getAuth, signOut } from "firebase/auth";
+import MenuItem from '@/components/MenuItem';
-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);
- }
+import { getAuth, signOut } from "firebase/auth";
+import { getFirestore, doc, getDoc, collection, query, where, getDocs } from "firebase/firestore";
+
+const ProfileCard = ({ cafeName, cafeAddress, city, phone }: { cafeName: string, cafeAddress: string, city: string, phone: string }) => (
+
+
+
+
+ {cafeName || "Nom De Café"}
+
+
+ Validé
+
+
+
+
+ {cafeAddress || "Adresse Du Café"}, {city}
+
+
+
+ +216 {phone || "+216 00 000 000"}
+
+
+);
+
+const handleSignOut = () => {
+ Alert.alert(
+ "Déconnexion",
+ "Êtes-vous sûr de vouloir vous déconnecter ?",
+ [
+ { text: "Annuler", style: "cancel" },
+ {
+ text: "Déconnexion",
+ style: "destructive",
+ onPress: async () => {
+ try {
+ const auth = getAuth();
+ await signOut(auth);
+ router.replace('/');
+ } catch (error) {
+ Alert.alert("Erreur", "Impossible de se déconnecter.");
}
}
- ]
+ }
+ ]
+ );
+};
+
+export default function ProfileScreen() {
+ const [cafeName, setCafeName] = useState('');
+ const [cafeAddress, setCafeAddress] = useState('');
+ const [city, setCity] = useState('');
+ const [phone, setPhone] = useState('');
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState('');
+
+ useEffect(() => {
+ const fetchProfileData = async () => {
+ try {
+ setLoading(true);
+ const auth = getAuth();
+ const db = getFirestore();
+ const user = auth.currentUser;
+
+ if (!user) {
+ setError("Utilisateur non connecté.");
+ return;
+ }
+
+ // Fetch téléphone
+ const userDoc = await getDoc(doc(db, 'users', user.uid));
+ if (userDoc.exists()) {
+ const userData = userDoc.data();
+ setPhone(userData.phone || '');
+ } else {
+ setError("Données utilisateur non trouvées.");
+ }
+
+ // Fetch café
+ const q = query(collection(db, 'coffee_shops'), where('ownerId', '==', user.uid));
+ const querySnapshot = await getDocs(q);
+ if (!querySnapshot.empty) {
+ const cafeData = querySnapshot.docs[0].data();
+ setCafeName(cafeData.cafeName || '');
+ setCafeAddress(cafeData.cafeAddress || '');
+ setCity(cafeData.city || '');
+ } else {
+ setError("Aucune information de café trouvée.");
+ }
+
+ } catch (error) {
+ setError("Erreur lors du chargement des données.");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchProfileData();
+ }, []);
+
+ if (loading) {
+ return (
+
+
+
+ Chargement des données...
+
+
);
- };
+ }
+
+ if (error) {
+ return (
+
+
+ {error}
+ router.back()} style={{ marginTop: 20 }}>
+ Retour
+
+
+
+ );
+ }
return (
-
-
- router.back()}>
-
-
- Espace Personnel
-
+
+
+
+
+ router.back()}>
+
+
+ Espace Personnel
+
-
-
- Se déconnecter
-
-
-
+
+
+
+ Commandes
+
+
+
+ Paramètres
+
+
+
+
+ Déconnexion
+
+
+
+
);
}
@@ -57,7 +173,6 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
position: 'relative',
- backgroundColor: '#fff',
borderBottomWidth: 1,
borderBottomColor: '#eee',
marginTop: 5,
@@ -73,20 +188,71 @@ const styles = StyleSheet.create({
fontWeight: '600',
color: '#333',
},
- body: {
- flex: 1,
- justifyContent: 'center',
+ profileCard: {
+ backgroundColor: '#f8f3e9',
+ borderRadius: 12,
+ padding: 20,
+ marginBottom: 32,
+ marginVertical: 25,
+ marginHorizontal: 15,
+ },
+ profileHeader: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'flex-start',
+ marginBottom: 15,
+ },
+ profileTitle: {
+ flexDirection: 'row',
alignItems: 'center',
+ gap: 8,
},
- signOutButton: {
- backgroundColor: '#B07B4F',
- paddingVertical: 12,
- paddingHorizontal: 24,
- borderRadius: 10,
- },
- signOutButtonText: {
- color: '#fff',
+ cafeName: {
fontSize: 16,
- fontWeight: '600',
+ fontWeight: '700',
+ color: '#1F2937',
+ },
+ badge: {
+ backgroundColor: '#10B981',
+ paddingHorizontal: 12,
+ paddingVertical: 4,
+ borderRadius: 9999,
+ },
+ badgeText: {
+ color: 'white',
+ fontSize: 12,
+ fontWeight: '500',
+ },
+ profileInfo: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 8,
+ marginBottom: 8,
+ },
+ infoText: {
+ fontSize: 14,
+ color: '#4B5563',
+ },
+ section: {
+ marginBottom: 16,
+ },
+ sectionTitle: {
+ fontSize: 18,
+ fontWeight: '700',
+ color: '#1F2937',
+ marginBottom: 8,
+ marginHorizontal: 15
+ },
+ logoutButton: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 8,
+ paddingVertical: 16,
+ marginHorizontal: 15
+ },
+ logoutText: {
+ color: '#EF4444',
+ fontSize: 15,
+ fontWeight: '500',
},
});
diff --git a/app/screens/user/_layout.tsx b/app/screens/user/_layout.tsx
index d299b39..414a52d 100644
--- a/app/screens/user/_layout.tsx
+++ b/app/screens/user/_layout.tsx
@@ -52,6 +52,12 @@ export default function UserLayout() {
href: null,
}}
/>
+
);
diff --git a/components/MenuItem.tsx b/components/MenuItem.tsx
new file mode 100644
index 0000000..a68b603
--- /dev/null
+++ b/components/MenuItem.tsx
@@ -0,0 +1,70 @@
+import React from 'react';
+import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
+import { Feather } from '@expo/vector-icons';
+
+type MenuItemProps = {
+ icon: string;
+ label: string;
+ badge?: number;
+ onPress?: () => void;
+};
+
+const MenuItem = ({ icon, label, badge, onPress }: MenuItemProps) => {
+ return (
+
+
+
+ {label}
+
+
+ {badge ? (
+
+ {badge}
+
+ ) : null}
+
+
+
+ );
+};
+
+export default MenuItem;
+
+const styles = StyleSheet.create({
+ menuItem: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ paddingVertical: 16,
+ borderBottomWidth: 1,
+ borderBottomColor: '#E5E7EB',
+ marginHorizontal: 15
+ },
+ menuItemLeft: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 12,
+ },
+ menuItemText: {
+ fontSize: 15,
+ color: '#374151',
+ },
+ menuItemRight: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 8,
+ },
+ menuBadge: {
+ backgroundColor: '#F59E0B',
+ width: 20,
+ height: 20,
+ borderRadius: 10,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ menuBadgeText: {
+ color: 'white',
+ fontSize: 12,
+ fontWeight: '500',
+ },
+});
diff --git a/components/OrderItem.tsx b/components/OrderItem.tsx
new file mode 100644
index 0000000..479ac21
--- /dev/null
+++ b/components/OrderItem.tsx
@@ -0,0 +1,118 @@
+import React from 'react';
+import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
+import { Order } from '@/constants/types';
+import Animated, {
+ useAnimatedStyle,
+ useSharedValue,
+ withTiming,
+ Easing,
+} from 'react-native-reanimated';
+import COLORS from '@/constants/colors'; // Make sure you have COLORS imported if not already
+
+interface OrderItemProps {
+ order: Order;
+}
+
+export default function OrderItem({ order }: OrderItemProps) {
+ const scale = useSharedValue(1);
+
+ const animatedStyle = useAnimatedStyle(() => {
+ return {
+ transform: [{ scale: scale.value }],
+ };
+ });
+
+ const handlePressIn = () => {
+ scale.value = withTiming(0.98, {
+ duration: 100,
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1),
+ });
+ };
+
+ const handlePressOut = () => {
+ scale.value = withTiming(1, {
+ duration: 200,
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1),
+ });
+ };
+
+ // Conditional status logic
+ const statusBackgroundColor = order.status === "Confirmée" ? "#4C7C54" : COLORS.primary;
+ const statusText = order.status === "Confirmée" ? "Terminée" : order.status;
+
+ return (
+
+
+
+ {/* Show the orderId */}
+ Commande #{order.orderId}
+
+ {statusText}
+
+
+
+ {order.items.map((item, index) => (
+
+ {item.name} x {item.quantity}
+
+ ))}
+
+ {order.timeAgo}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ backgroundColor: '#F2F2F2',
+ borderRadius: 12,
+ padding: 25,
+ shadowColor: '#000',
+ shadowOffset: {
+ width: 0,
+ height: 1,
+ },
+ shadowOpacity: 0.05,
+ shadowRadius: 3,
+ elevation: 2,
+ },
+ header: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ marginBottom: 8,
+ },
+ orderId: {
+ fontSize: 16,
+ fontWeight: '600',
+ color: '#333333',
+ },
+ statusContainer: {
+ paddingHorizontal: 8,
+ paddingVertical: 4,
+ borderRadius: 4,
+ },
+ statusText: {
+ color: '#FFFFFF',
+ fontSize: 12,
+ fontWeight: '500',
+ },
+ itemsContainer: {
+ marginBottom: 8,
+ },
+ item: {
+ fontSize: 14,
+ color: '#666666',
+ lineHeight: 20,
+ },
+ time: {
+ fontSize: 12,
+ color: '#999999',
+ alignSelf: 'flex-end',
+ },
+});
diff --git a/components/OrderList.tsx b/components/OrderList.tsx
new file mode 100644
index 0000000..634fa1d
--- /dev/null
+++ b/components/OrderList.tsx
@@ -0,0 +1,120 @@
+import React, { useState, useEffect } from 'react';
+import { FlatList, StyleSheet, View, Text, Alert, RefreshControl } from 'react-native';
+import Animated, { FadeInUp } from 'react-native-reanimated';
+import OrderItem from '@/components/OrderItem';
+import { getAuth } from 'firebase/auth';
+import { collection, query, where, getDocs } from 'firebase/firestore';
+import { db } from '@/firebase/config';
+import { Order } from '@/constants/types';
+import COLORS from '@/constants/colors';
+
+interface OrderListProps {
+ // Accept a trigger or prop to refresh data
+}
+
+export default function OrderList() {
+ const [orders, setOrders] = useState([]);
+ const [refreshing, setRefreshing] = useState(false);
+
+ const fetchOrders = async () => {
+ const user = getAuth().currentUser;
+ if (!user) {
+ Alert.alert('Erreur', 'Utilisateur non connecté');
+ return;
+ }
+
+ try {
+ const ordersQuery = query(
+ collection(db, 'orders'),
+ where('userId', '==', user.uid)
+ );
+
+ const querySnapshot = await getDocs(ordersQuery);
+
+ const ordersData = querySnapshot.docs.map((doc) => {
+ const data = doc.data();
+ const order: Order = {
+ id: doc.id,
+ orderId: data.orderId || '',
+ items: Object.keys(data)
+ .filter(key => key.startsWith('item'))
+ .map((key) => ({
+ name: data[key].name,
+ quantity: data[key].quantity,
+ })),
+ status: data.status || '',
+ timeAgo: data.createdAt ? new Date(data.createdAt).toLocaleString() : '',
+ userId: data.userId || '',
+ createdAt: data.createdAt ? new Date(data.createdAt.seconds * 1000) : new Date(), // Ensure it is a Date object
+ };
+ return order;
+ });
+
+ // Sort by orderId in descending order
+ const sortedOrders = ordersData.sort((a, b) =>
+ b.orderId.localeCompare(a.orderId) // reverse of localeCompare
+ );
+
+ setOrders(sortedOrders);
+ } catch (error) {
+ console.error('Erreur lors de la récupération des commandes:', error);
+ Alert.alert('Erreur', "Impossible de récupérer les commandes.");
+ } finally {
+ setRefreshing(false); // stop the refreshing indicator after fetch
+ }
+ };
+
+ const onRefresh = () => {
+ setRefreshing(true);
+ fetchOrders();
+ };
+
+ useEffect(() => {
+ fetchOrders();
+ }, []);
+
+ return (
+ item.id}
+ renderItem={({ item, index }) => (
+
+
+
+ )}
+ contentContainerStyle={styles.listContent}
+ showsVerticalScrollIndicator={false}
+ ListEmptyComponent={
+
+ Aucune commande trouvée
+
+ }
+ refreshControl={
+
+ }
+ />
+ );
+}
+
+const styles = StyleSheet.create({
+ listContent: {
+ paddingTop: 16,
+ paddingBottom: 24,
+ },
+ itemContainer: {
+ marginBottom: 12,
+ },
+ emptyContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ paddingVertical: 10,
+ },
+ emptyText: {
+ color: COLORS.text,
+ fontSize: 18,
+ },
+});
diff --git a/components/ProductCard.tsx b/components/ProductCard.tsx
index fe7dda1..27b41cd 100644
--- a/components/ProductCard.tsx
+++ b/components/ProductCard.tsx
@@ -22,7 +22,6 @@ export default function ProductCard({
inStock = true,
}: ProductCardProps) {
const router = useRouter();
- console.log(image); // Log the image URL to ensure it's correct
return (
diff --git a/constants/types.ts b/constants/types.ts
index 10974ce..d7c6c93 100644
--- a/constants/types.ts
+++ b/constants/types.ts
@@ -17,4 +17,19 @@ export interface Product {
export interface CartState {
items: CartItem[];
total: number;
- }
\ No newline at end of file
+ }
+
+ export interface Order {
+ id: string;
+ orderId:string,
+ items: OrderItem[];
+ status: string;
+ timeAgo: string;
+ userId: string;
+ createdAt: Date;
+ }
+ export interface OrderItem {
+ name: string;
+ quantity: number;
+ }
+
\ No newline at end of file