client v1/notifs
This commit is contained in:
parent
bb4befc189
commit
3b8e19b211
@ -1,5 +1,5 @@
|
|||||||
import { useState } from "react";
|
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 { Eye, EyeOff,ArrowLeft,Check } from "lucide-react-native";
|
||||||
import { router } from "expo-router";
|
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
|
||||||
@ -10,6 +10,7 @@ import AsyncStorage from "@react-native-async-storage/async-storage";
|
|||||||
|
|
||||||
|
|
||||||
const SignInScreen = () => {
|
const SignInScreen = () => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
email: "",
|
email: "",
|
||||||
password: "",
|
password: "",
|
||||||
@ -43,7 +44,7 @@ const SignInScreen = () => {
|
|||||||
|
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
if (!validateForm()) return;
|
if (!validateForm()) return;
|
||||||
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const { user } = await signIn(form.email, form.password);
|
const { user } = await signIn(form.email, form.password);
|
||||||
|
|
||||||
@ -57,7 +58,9 @@ const SignInScreen = () => {
|
|||||||
|
|
||||||
router.replace("/screens/user/UserHomeScreen");
|
router.replace("/screens/user/UserHomeScreen");
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
Alert.alert("Erreur", error.message);
|
Alert.alert("Erreur", "Utilisateur Introuvable");
|
||||||
|
} finally{
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -159,9 +162,17 @@ const SignInScreen = () => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Pressable style={styles.loginButton} onPress={handleLogin}>
|
<TouchableOpacity
|
||||||
<Text style={styles.loginButtonText}>Connexion</Text>
|
style={[styles.loginButton, loading && { opacity: 0.7 }]}
|
||||||
</Pressable>
|
onPress={handleLogin}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<ActivityIndicator color="#fff" />
|
||||||
|
) : (
|
||||||
|
<Text style={styles.loginButtonText}>Connexion</Text>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
<View style={styles.signupContainer}>
|
<View style={styles.signupContainer}>
|
||||||
<Text style={styles.signupText}>Nouveau ici? </Text>
|
<Text style={styles.signupText}>Nouveau ici? </Text>
|
||||||
|
@ -7,12 +7,14 @@ import {
|
|||||||
FlatList,
|
FlatList,
|
||||||
Platform,
|
Platform,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
Button,
|
Button
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { useCart } from '@/context/cartContext';
|
import { useCart } from '@/context/cartContext';
|
||||||
import { getAuth } from 'firebase/auth';
|
import { getAuth } from 'firebase/auth';
|
||||||
import { collection, addDoc } from 'firebase/firestore';
|
import { collection, addDoc } from 'firebase/firestore';
|
||||||
|
import { query, orderBy, limit, getDocs } from 'firebase/firestore';
|
||||||
|
|
||||||
import { db } from '@/firebase/config';
|
import { db } from '@/firebase/config';
|
||||||
import CartItem from '@/components/CartItem';
|
import CartItem from '@/components/CartItem';
|
||||||
import { ArrowLeft } from 'lucide-react-native';
|
import { ArrowLeft } from 'lucide-react-native';
|
||||||
@ -44,39 +46,61 @@ export default function CartScreen() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const handleConfirmOrder = useCallback(async () => {
|
const handleConfirmOrder = useCallback(async () => {
|
||||||
|
|
||||||
|
|
||||||
if (totalAmount <= 0) {
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsProcessingOrder(true);
|
setIsProcessingOrder(true);
|
||||||
buttonScale.value = withSequence(
|
buttonScale.value = withSequence(
|
||||||
withTiming(0.95, { duration: 100 }),
|
withTiming(0.95, { duration: 100 }),
|
||||||
withTiming(1, { duration: 150 })
|
withTiming(1, { duration: 150 })
|
||||||
);
|
);
|
||||||
|
|
||||||
if (Platform.OS !== 'web') {
|
if (Platform.OS !== 'web') {
|
||||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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 = {
|
const orderData: any = {
|
||||||
|
orderId: newOrderId, // Use the generated ID
|
||||||
|
userId: user.uid,
|
||||||
status: 'En attente',
|
status: 'En attente',
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
totalAmount: totalAmount
|
totalAmount: totalAmount,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add each item in the cart to the orderData
|
||||||
cart.items.forEach((item, index) => {
|
cart.items.forEach((item, index) => {
|
||||||
orderData[`item${index + 1}`] = {
|
orderData[`item${index + 1}`] = {
|
||||||
name: item.name,
|
name: item.name,
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add the order to Firestore
|
||||||
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', `Montant Total : ${totalAmount}DT`);
|
||||||
|
|
||||||
clearCart();
|
clearCart();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur commande :', error);
|
console.error('Erreur commande :', error);
|
||||||
@ -84,7 +108,8 @@ export default function CartScreen() {
|
|||||||
} finally {
|
} finally {
|
||||||
setIsProcessingOrder(false);
|
setIsProcessingOrder(false);
|
||||||
}
|
}
|
||||||
}, [cart, totalAmount]);
|
}, [cart, totalAmount, auth]);
|
||||||
|
|
||||||
|
|
||||||
const renderItem = useCallback(({ item }: { item: CartItemType }) => (
|
const renderItem = useCallback(({ item }: { item: CartItemType }) => (
|
||||||
<CartItem
|
<CartItem
|
||||||
@ -169,7 +194,8 @@ export default function CartScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: { flex: 1, backgroundColor: '#fff' },
|
container: { flex: 1,
|
||||||
|
backgroundColor: '#fff' },
|
||||||
header: {
|
header: {
|
||||||
height: 60,
|
height: 60,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
59
app/screens/user/OrderHistoryScreen.tsx
Normal file
59
app/screens/user/OrderHistoryScreen.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { View, StyleSheet,Text,TouchableOpacity } from 'react-native';
|
||||||
|
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
|
import { router } from "expo-router";
|
||||||
|
import { ArrowLeft } from 'lucide-react-native';
|
||||||
|
import OrderList from '@/components/OrderList';
|
||||||
|
|
||||||
|
|
||||||
|
export default function OrderHistoryScreen() {
|
||||||
|
return (
|
||||||
|
<SafeAreaProvider>
|
||||||
|
<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}>Historique d'achat</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.content}>
|
||||||
|
<OrderList />
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
|
</SafeAreaProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
@ -1,49 +1,165 @@
|
|||||||
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Alert } from 'react-native';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
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 { ArrowLeft } from 'lucide-react-native';
|
||||||
import { router } from "expo-router";
|
import { router } from "expo-router";
|
||||||
import { getAuth, signOut } from "firebase/auth";
|
import MenuItem from '@/components/MenuItem';
|
||||||
|
|
||||||
export default function ProfileScreen() {
|
import { getAuth, signOut } from "firebase/auth";
|
||||||
const handleSignOut = () => {
|
import { getFirestore, doc, getDoc, collection, query, where, getDocs } from "firebase/firestore";
|
||||||
Alert.alert(
|
|
||||||
"Déconnexion",
|
const ProfileCard = ({ cafeName, cafeAddress, city, phone }: { cafeName: string, cafeAddress: string, city: string, phone: string }) => (
|
||||||
"Êtes-vous sûr de vouloir vous déconnecter ?",
|
<View style={styles.profileCard}>
|
||||||
[
|
<View style={styles.profileHeader}>
|
||||||
{ text: "Annuler", style: "cancel" },
|
<View style={styles.profileTitle}>
|
||||||
{
|
<Feather name="coffee" size={20} color="#B45309" />
|
||||||
text: "Se déconnecter",
|
<Text style={styles.cafeName}>{cafeName || "Nom De Café"}</Text>
|
||||||
style: "destructive",
|
</View>
|
||||||
onPress: async () => {
|
<View style={styles.badge}>
|
||||||
try {
|
<Text style={styles.badgeText}>Validé</Text>
|
||||||
const auth = getAuth();
|
</View>
|
||||||
await signOut(auth);
|
</View>
|
||||||
router.replace('/'); // Replace with your actual sign-in screen route
|
<View style={styles.profileInfo}>
|
||||||
} catch (error) {
|
<Feather name="map-pin" size={16} color="#4B5563" />
|
||||||
Alert.alert("Erreur", "Impossible de se déconnecter.");
|
<Text style={styles.infoText}>{cafeAddress || "Adresse Du Café"}, {city}</Text>
|
||||||
console.error("Sign out error:", error);
|
</View>
|
||||||
}
|
<View style={styles.profileInfo}>
|
||||||
|
<Feather name="phone" size={16} color="#4B5563" />
|
||||||
|
<Text style={styles.infoText}>+216 {phone || "+216 00 000 000"}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||||
|
<ActivityIndicator size="large" color="#B45309" />
|
||||||
|
<Text style={{ marginTop: 12 }}>Chargement des données...</Text>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 }}>
|
||||||
|
<Text style={{ color: 'red', fontSize: 16, textAlign: 'center' }}>{error}</Text>
|
||||||
|
<TouchableOpacity onPress={() => router.back()} style={{ marginTop: 20 }}>
|
||||||
|
<Text style={{ color: '#2563EB' }}>Retour</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container}>
|
<SafeAreaProvider>
|
||||||
<View style={styles.header}>
|
<SafeAreaView style={styles.container}>
|
||||||
<TouchableOpacity style={styles.backButton} onPress={() => router.back()}>
|
<ScrollView>
|
||||||
<ArrowLeft size={24} color="#666" />
|
<View style={styles.header}>
|
||||||
</TouchableOpacity>
|
<TouchableOpacity style={styles.backButton} onPress={() => router.back()}>
|
||||||
<Text style={styles.headerTitle}>Espace Personnel</Text>
|
<ArrowLeft size={24} color="#666" />
|
||||||
</View>
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.headerTitle}>Espace Personnel</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View style={styles.body}>
|
<ProfileCard cafeName={cafeName} cafeAddress={cafeAddress} city={city} phone={phone} />
|
||||||
<TouchableOpacity style={styles.signOutButton} onPress={handleSignOut}>
|
|
||||||
<Text style={styles.signOutButtonText}>Se déconnecter</Text>
|
<View style={styles.section}>
|
||||||
</TouchableOpacity>
|
<Text style={styles.sectionTitle}>Commandes</Text>
|
||||||
</View>
|
<MenuItem icon="clock" label="Historique d'achats" onPress={() => router.push('/screens/user/OrderHistoryScreen')} />
|
||||||
</SafeAreaView>
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Paramètres</Text>
|
||||||
|
<MenuItem icon="bell" label="Notifications" badge={2}/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.logoutButton} onPress={handleSignOut}>
|
||||||
|
<Feather name="log-out" size={20} color="#EF4444" />
|
||||||
|
<Text style={styles.logoutText}>Déconnexion</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
</SafeAreaProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +173,6 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
backgroundColor: '#fff',
|
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: '#eee',
|
borderBottomColor: '#eee',
|
||||||
marginTop: 5,
|
marginTop: 5,
|
||||||
@ -73,20 +188,71 @@ const styles = StyleSheet.create({
|
|||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: '#333',
|
color: '#333',
|
||||||
},
|
},
|
||||||
body: {
|
profileCard: {
|
||||||
flex: 1,
|
backgroundColor: '#f8f3e9',
|
||||||
justifyContent: 'center',
|
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',
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
},
|
},
|
||||||
signOutButton: {
|
cafeName: {
|
||||||
backgroundColor: '#B07B4F',
|
|
||||||
paddingVertical: 12,
|
|
||||||
paddingHorizontal: 24,
|
|
||||||
borderRadius: 10,
|
|
||||||
},
|
|
||||||
signOutButtonText: {
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: 16,
|
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',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -52,6 +52,12 @@ export default function UserLayout() {
|
|||||||
href: null,
|
href: null,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Tabs.Screen
|
||||||
|
name="OrderHistoryScreen"
|
||||||
|
options={{
|
||||||
|
href: null,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
);
|
);
|
||||||
|
70
components/MenuItem.tsx
Normal file
70
components/MenuItem.tsx
Normal file
@ -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 (
|
||||||
|
<TouchableOpacity style={styles.menuItem} onPress={onPress}>
|
||||||
|
<View style={styles.menuItemLeft}>
|
||||||
|
<Feather name={icon as any} size={20} color="#374151" />
|
||||||
|
<Text style={styles.menuItemText}>{label}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.menuItemRight}>
|
||||||
|
{badge ? (
|
||||||
|
<View style={styles.menuBadge}>
|
||||||
|
<Text style={styles.menuBadgeText}>{badge}</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
<Feather name="chevron-right" size={18} color="#9CA3AF" />
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
});
|
118
components/OrderItem.tsx
Normal file
118
components/OrderItem.tsx
Normal file
@ -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 (
|
||||||
|
<TouchableOpacity
|
||||||
|
activeOpacity={0.9}
|
||||||
|
onPressIn={handlePressIn}
|
||||||
|
onPressOut={handlePressOut}
|
||||||
|
>
|
||||||
|
<Animated.View style={[styles.container, animatedStyle]}>
|
||||||
|
<View style={styles.header}>
|
||||||
|
{/* Show the orderId */}
|
||||||
|
<Text style={styles.orderId}>Commande #{order.orderId}</Text>
|
||||||
|
<View style={[styles.statusContainer, { backgroundColor: statusBackgroundColor }]}>
|
||||||
|
<Text style={styles.statusText}>{statusText}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={styles.itemsContainer}>
|
||||||
|
{order.items.map((item, index) => (
|
||||||
|
<Text key={index} style={styles.item}>
|
||||||
|
{item.name} x {item.quantity}
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
<Text style={styles.time}>{order.timeAgo}</Text>
|
||||||
|
</Animated.View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
});
|
120
components/OrderList.tsx
Normal file
120
components/OrderList.tsx
Normal file
@ -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<Order[]>([]);
|
||||||
|
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 (
|
||||||
|
<FlatList
|
||||||
|
data={orders}
|
||||||
|
keyExtractor={(item) => item.id}
|
||||||
|
renderItem={({ item, index }) => (
|
||||||
|
<Animated.View
|
||||||
|
entering={FadeInUp.delay(index * 100).springify()}
|
||||||
|
style={styles.itemContainer}
|
||||||
|
>
|
||||||
|
<OrderItem order={item} />
|
||||||
|
</Animated.View>
|
||||||
|
)}
|
||||||
|
contentContainerStyle={styles.listContent}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
ListEmptyComponent={
|
||||||
|
<View style={styles.emptyContainer}>
|
||||||
|
<Text style={styles.emptyText}>Aucune commande trouvée</Text>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
@ -22,7 +22,6 @@ export default function ProductCard({
|
|||||||
inStock = true,
|
inStock = true,
|
||||||
}: ProductCardProps) {
|
}: ProductCardProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
console.log(image); // Log the image URL to ensure it's correct
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -17,4 +17,19 @@ export interface Product {
|
|||||||
export interface CartState {
|
export interface CartState {
|
||||||
items: CartItem[];
|
items: CartItem[];
|
||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Order {
|
||||||
|
id: string;
|
||||||
|
orderId:string,
|
||||||
|
items: OrderItem[];
|
||||||
|
status: string;
|
||||||
|
timeAgo: string;
|
||||||
|
userId: string;
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
export interface OrderItem {
|
||||||
|
name: string;
|
||||||
|
quantity: number;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user