Compare commits

...

3 Commits

Author SHA1 Message Date
befa166ef5 order submission v1 2025-04-18 06:09:09 +01:00
83346c7ba5 fetching products data 2025-04-18 03:41:15 +01:00
b77ccc6c50 user's screen ui v1 2025-04-17 05:58:59 +01:00
17 changed files with 825 additions and 119 deletions

View File

@ -3,12 +3,15 @@ import { Stack } from 'expo-router';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import React from 'react';
import { CartProvider } from './context/cartContext'
export default function Layout() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<SafeAreaProvider>
<CartProvider>
<Stack screenOptions={{ headerShown: false}} />
</CartProvider>
</SafeAreaProvider>
</GestureHandlerRootView>
);

107
app/components/CartItem.tsx Normal file
View File

@ -0,0 +1,107 @@
import React from 'react';
import { View, Text, StyleSheet, Image } from 'react-native';
import COLORS from "@/app/constants/colors";
import { CartItem as CartItemType } from '@/app/constants/types';
import QuantityControl from '@/app/components/QuantityControl';
import Animated, {
useAnimatedStyle,
withTiming,
useSharedValue,
runOnJS
} from 'react-native-reanimated';
interface CartItemProps {
item: CartItemType;
onUpdateQuantity: (id: string, quantity: number) => void;
}
const CartItem: React.FC<CartItemProps> = ({ item, onUpdateQuantity }) => {
const opacity = useSharedValue(1);
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => {
return {
opacity: opacity.value,
transform: [{ scale: scale.value }]
};
});
const handleIncrease = () => {
scale.value = withTiming(1.02, { duration: 100 }, () => {
scale.value = withTiming(1, { duration: 100 });
});
onUpdateQuantity(item.id, item.quantity + 1);
};
const handleDecrease = () => {
if (item.quantity > 0) {
scale.value = withTiming(0.98, { duration: 100 }, () => {
scale.value = withTiming(1, { duration: 100 });
});
onUpdateQuantity(item.id, item.quantity - 1);
}
};
return (
<Animated.View style={[styles.container, animatedStyle]}>
<View style={styles.imageContainer}>
<Image source={{ uri: item.image }} style={styles.image} />
</View>
<View style={styles.contentContainer}>
<Text style={styles.name}>{item.name}</Text>
<View style={styles.controlRow}>
<QuantityControl
quantity={item.quantity}
onIncrease={handleIncrease}
onDecrease={handleDecrease}
/>
<Text style={styles.price}>{item.price}{item.priceUnit}</Text>
</View>
</View>
</Animated.View>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
backgroundColor: COLORS.secondary,
borderRadius: 12,
marginBottom: 5,
padding: 4,
},
imageContainer: {
width: 80,
height: 80,
borderRadius: 8,
overflow: 'hidden',
marginRight: 2,
},
image: {
width: '100%',
height: '100%',
},
contentContainer: {
flex: 1,
justifyContent: 'center',
},
name: {
fontSize: 16,
color: COLORS.text,
marginBottom: 5,
},
controlRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
price: {
fontSize: 14,
color: COLORS.secondary,
},
});
export default CartItem;

View File

@ -1,20 +1,21 @@
import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native';
import { useRouter } from 'expo-router';
import { ChevronRight } from 'lucide-react-native';
import COLORS from "@/app/constants/colors";
import COLORS from '@/app/constants/colors';
import { Product } from '../constants/types';
interface ProductCardProps {
id: string;
name: string;
productName: string;
image: string;
description: string;
price: number;
price: string;
inStock?: boolean;
}
export default function ProductCard({
id,
name,
productName,
image,
description,
price,
@ -22,27 +23,40 @@ export default function ProductCard({
}: ProductCardProps) {
const router = useRouter();
return (
<TouchableOpacity
style={styles.card}
onPress={() =>
router.push({
pathname: '/screens/user/ProductDetailsScreen', // Absolute path for reliability
params: { productId: id },
})
}
>
<Image source={{ uri: image }} style={styles.image} />
<Image
source={{ uri: image }}
style={styles.image}
resizeMode="cover"
onError={(error) => console.log('Image loading error:', error.nativeEvent.error)}
/>
<View style={styles.content}>
<View>
<Text style={styles.title}>{name}</Text>
<Text style={styles.title}>{productName}</Text>
<Text style={styles.blend}>
{description}
</Text>
</View>
<View style={styles.rightContent}>
<ChevronRight size={20} color="#666" />
<Text style={[styles.stock, { color: inStock ? '#22C55E' : '#EF4444' }]}>
{inStock ? 'En Stock' : 'Hors Stock'}
</Text>
<View style={styles.priceContainer}>
<Text style={styles.price}>{price}</Text>
<Text style={styles.unit}>TND/kg</Text>
</View>
<ChevronRight size={20} color="#666" />
<Text style={[styles.stock, { color: inStock ? '#22C55E' : '#EF4444' }]}>
{inStock ? 'En Stock' : 'Hors Stock'}
</Text>
<View style={styles.priceContainer}>
<Text style={styles.price}>{price}</Text>
<Text style={styles.unit}>TND/kg</Text>
</View>
</View>
</View>
</TouchableOpacity>
@ -70,6 +84,7 @@ const styles = StyleSheet.create({
width: 75,
height: 120,
borderRadius: 8,
backgroundColor: '#eee', // Fallback while loading
},
content: {
flex: 1,
@ -81,7 +96,7 @@ const styles = StyleSheet.create({
title: {
fontSize: 17,
fontWeight: '700',
color: '#333',
color: '#000',
marginBottom: 4,
},
blend: {
@ -92,12 +107,12 @@ const styles = StyleSheet.create({
flex: 1,
justifyContent: 'space-between',
alignItems: 'flex-end',
height: 110, // Assure un espacement vertical régulier
height: 110,
},
priceContainer: {
flexDirection: 'row',
alignItems: 'baseline',
},
},
price: {
fontSize: 18,
fontWeight: '600',
@ -111,6 +126,5 @@ const styles = StyleSheet.create({
},
stock: {
fontSize: 12,
color: '#22C55E',
},
});

View File

@ -0,0 +1,86 @@
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Platform } from 'react-native';
import COLORS from "@/app/constants/colors";
import { Minus, Plus } from 'lucide-react-native';
import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated';
import * as Haptics from 'expo-haptics';
interface QuantityControlProps {
quantity: number;
onIncrease: () => void;
onDecrease: () => void;
}
const QuantityControl: React.FC<QuantityControlProps> = ({
quantity,
onIncrease,
onDecrease
}) => {
const animatedTextStyle = useAnimatedStyle(() => {
return {
opacity: 1,
};
});
const handleIncrease = () => {
onIncrease();
if (Platform.OS !== 'web') {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
}
};
const handleDecrease = () => {
if (quantity > 0) {
onDecrease();
if (Platform.OS !== 'web') {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
}
}
};
return (
<View style={styles.container}>
<TouchableOpacity
style={[styles.button, { opacity: quantity <= 0 ? 0.5 : 1 }]}
onPress={handleDecrease}
disabled={quantity <= 0}
>
<Minus size={16} color={COLORS.text} />
</TouchableOpacity>
<Animated.Text style={[styles.quantity, animatedTextStyle]}>
{quantity}
</Animated.Text>
<TouchableOpacity style={styles.button} onPress={handleIncrease}>
<Plus size={16} color={COLORS.text} />
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
height: 36,
},
button: {
width: 36,
height: 36,
backgroundColor: COLORS.secondary,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 6,
},
quantity: {
fontSize: 14,
color: COLORS.text,
paddingHorizontal: 6,
minWidth: 40,
textAlign: 'center',
},
});
export default QuantityControl;

21
app/constants/types.ts Normal file
View File

@ -0,0 +1,21 @@
export interface Product {
id: string;
productName: string;
image: string;
description: string;
price: string;
inStock: boolean;
}
export interface CartItem {
id: string;
name: string;
price: number;
priceUnit: string;
quantity: number;
image: string;
}
export interface CartState {
items: CartItem[];
total: number;
}

View File

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

View File

@ -10,7 +10,6 @@ import COLORS from "@/app/constants/colors";
const SignInScreen = () => {
const [step, setStep] = useState(1);
const [form, setForm] = useState({
email: "",
password: "",

View File

@ -1,46 +1,131 @@
import { View, Text, StyleSheet, ScrollView,TouchableOpacity } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { ArrowLeft } from 'lucide-react-native';
import { router } from "expo-router";
import React, { useEffect, useState } from 'react';
import { useRouter } from 'expo-router';
import { View, Text, Button, Alert, StyleSheet } from 'react-native';
import { getAuth } from 'firebase/auth';
import { collection, addDoc } from 'firebase/firestore'; // Make sure these are imported
import { db } from '@/firebase/config'; // Ensure this path is correct
import { useCart } from '@/app/context/cartContext';
const CartScreen = () => {
const { cart, clearCart } = useCart();
const auth = getAuth();
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
const router = useRouter();
// Check if the user is logged in when the component mounts
useEffect(() => {
const user = auth.currentUser;
if (user) {
setIsAuthenticated(true);
} else {
setIsAuthenticated(false);
router.push('../screens/auth/SignInScreen'); // Redirect to sign-in screen if not logged in
}
}, [auth, router]);
const handleConfirmOrder = async () => {
if (!isAuthenticated) {
Alert.alert('Erreur', 'Utilisateur non connecté.');
return;
}
if (cart.total <= 0) {
Alert.alert('Panier vide', 'Veuillez ajouter des produits à votre panier');
return;
}
try {
const userId = auth.currentUser?.uid;
if (!userId) {
Alert.alert('Erreur', 'Utilisateur non connecté.');
return;
}
const orderData: any = {
userId,
status: 'En attente',
};
cart.items.forEach((item, index) => {
orderData[`item${index + 1}`] = [item.quantity, item.productName];
});
await addDoc(collection(db, 'orders'), orderData);
Alert.alert('Commande confirmée', 'Votre commande a été enregistrée.');
clearCart();
} catch (error) {
console.error('Erreur de commande', error);
Alert.alert('Erreur', "Impossible d'enregistrer la commande.");
}
};
if (!isAuthenticated) {
return <Text>Chargement...</Text>; // Loading state while checking authentication
}
export default function CartScreen() {
return (
<SafeAreaView style={styles.container}>
<View style={styles.header}>
<TouchableOpacity style={styles.backButton} onPress={() => router.back()}>
<ArrowLeft size={24} color="#666" />
</TouchableOpacity>
<Text style={styles.headerTitle}>Mon Panier</Text>
<View style={styles.container}>
<Text style={styles.title}>Votre Panier</Text>
{cart.items.length === 0 ? (
<Text style={styles.empty}>Votre panier est vide.</Text>
) : (
cart.items.map((item, index) => (
<View key={index} style={styles.itemContainer}>
<Text style={styles.itemText}>
{item.productName} - Quantité : {item.quantity}
</Text>
</View>
))
)}
{cart.items.length > 0 && (
<View style={styles.buttonContainer}>
<Button title="Confirmer la commande" onPress={handleConfirmOrder} color="#8B4513" />
<View style={{ marginTop: 10 }}>
<Button title="Vider le panier" onPress={clearCart} color="#A52A2A" />
</View>
</View>
</SafeAreaView>
)}
</View>
);
}
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
padding: 20,
backgroundColor: '#FFF',
flexGrow: 1,
},
header: {
height: 60,
justifyContent: 'center',
alignItems: 'center',
position: 'relative',
backgroundColor: '#fff',
borderBottomWidth: 1,
borderBottomColor: '#eee',
marginTop:5,
title: {
fontSize: 22,
fontWeight: 'bold',
marginBottom: 15,
textAlign: 'center',
color: '#5A3E36',
},
backButton: {
position: 'absolute',
left: 20,
top: '50%',
transform: [{ translateY: -12 }],
empty: {
fontSize: 16,
color: '#777',
textAlign: 'center',
marginTop: 50,
},
headerTitle: {
fontSize: 20,
fontWeight: '600',
itemContainer: {
backgroundColor: '#F5F5DC',
padding: 15,
marginBottom: 10,
borderRadius: 10,
borderColor: '#DDD',
borderWidth: 1,
},
itemText: {
fontSize: 16,
color: '#333',
},
});
buttonContainer: {
marginTop: 30,
},
});
export default CartScreen;

View File

@ -1,47 +1,33 @@
import { View, Text, StyleSheet, ScrollView,TouchableOpacity } from 'react-native';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { ArrowLeft } from 'lucide-react-native';
import { router } from "expo-router";
import ProductCard from '../../components/ProductCard';
import COLORS from '@/app/constants/colors';
const products = [
{
id: '1',
name: 'Café Brix Premium',
image: 'https://images.unsplash.com/photo-1559056199-641a0ac8b55e?w=200&h=300&fit=crop',
description: '50% robusta • 50% arabica',
price: 10,
inStock: true,
},
{
id: '2',
name: 'Café Brix Gold',
image: 'https://images.unsplash.com/photo-1587734361993-0275d60eb3d0?w=200&h=300&fit=crop',
description: '30% robusta • 70% arabica',
price: 12,
inStock: false,
},
{
id: '3',
name: 'Café Brix Prestige',
image: 'https://images.unsplash.com/photo-1559525839-b184a4d698c7?w=200&h=300&fit=crop',
description: '70% robusta • 30% arabica',
price: 11,
inStock: true,
},
{
id: '4',
name: 'Café Brix Turc',
image: 'https://images.unsplash.com/photo-1562447457-579fc34967fb?w=200&h=300&fit=crop',
description: '50% robusta • 50% arabica',
price: 10,
inStock: true,
},
];
import { collection, getDocs } from 'firebase/firestore';
import { useEffect, useState } from 'react';
import { db } from '@/firebase/config';
import { Product } from '@/app/constants/types';
export default function GrainsScreen() {
const [products, setProducts] = useState<Product[]>([]);
useEffect(() => {
const fetchProducts = async () => {
try {
const querySnapshot = await getDocs(collection(db, 'products'));
const items: Product[] = querySnapshot.docs.map(doc => ({
id: doc.id,
...(doc.data() as Omit<Product, 'id'>),
}));
setProducts(items);
} catch (error) {
console.error("Erreur lors de la récupération des produits :", error);
}
};
fetchProducts();
}, []);
return (
<SafeAreaView style={styles.container}>
<View style={styles.header}>
@ -49,7 +35,7 @@ export default function GrainsScreen() {
<ArrowLeft size={24} color="#666" />
</TouchableOpacity>
<Text style={styles.headerTitle}>Grains de café</Text>
</View>
</View>
<ScrollView style={styles.content}>
{products.map((product) => (
@ -73,7 +59,7 @@ const styles = StyleSheet.create({
backgroundColor: '#fff',
borderBottomWidth: 1,
borderBottomColor: '#eee',
marginTop:5,
marginTop: 5,
},
backButton: {
position: 'absolute',
@ -88,7 +74,7 @@ const styles = StyleSheet.create({
},
content: {
flex: 1,
padding: 8,
backgroundColor:COLORS.background_user ,
padding: 28,
backgroundColor: COLORS.background_user,
},
});
});

View File

@ -0,0 +1,291 @@
import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, Image, TouchableOpacity, ActivityIndicator,Alert } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { ChevronLeft, Minus, Plus } from 'lucide-react-native';
import { doc, getDoc } from 'firebase/firestore';
import { db } from '@/firebase/config';
import { useCart } from '@/app/context/cartContext';
import { Product } from '@/app/constants/types';
export default function ProductScreen() {
const { productId } = useLocalSearchParams();
const router = useRouter();
const [product, setProduct] = useState<Product | null>(null);
const [quantity, setQuantity] = useState(1);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Cart items state
const [cartItems, setCartItems] = useState<{ productName: string; quantity: number }[]>([]);
const fetchProductDetails = async () => {
if (!productId) {
setError('Aucun ID de produit fourni.');
setLoading(false);
return;
}
try {
setLoading(true);
setError(null);
const productDoc = await getDoc(doc(db, 'products', productId as string));
if (productDoc.exists()) {
setProduct({
id: productDoc.id,
...(productDoc.data() as Omit<Product, 'id'>),
});
setQuantity(1);
} else {
setError('Produit non trouvé.');
}
} catch (error) {
console.error('Erreur lors de la récupération du produit:', error);
setError('Impossible de charger le produit. Veuillez réessayer.');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchProductDetails();
}, [productId]);
// Add item to cart
const { addItem } = useCart();
const handleAddToCart = () => {
if (!product) return;
addItem({
productName: product?.productName,
quantity: quantity,
});
Alert.alert('Ajouté', `${quantity} ${product.productName} ajouté au panier`);
};
return (
<SafeAreaView style={styles.container}>
{/* Header */}
<View style={styles.header}>
<TouchableOpacity onPress={() => router.back()}>
<ChevronLeft size={24} color="#333" />
</TouchableOpacity>
<Text style={styles.title}>{product?.productName || 'Détails du produit'}</Text>
</View>
{/* Content */}
{loading ? (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#B07B4F" />
<Text style={styles.loadingText}>Chargement...</Text>
</View>
) : error ? (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>{error}</Text>
<TouchableOpacity
style={styles.retryButton}
onPress={() => {
setLoading(true);
fetchProductDetails();
}}
>
<Text style={styles.retryButtonText}>Réessayer</Text>
</TouchableOpacity>
</View>
) : !product ? (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>Produit non trouvé.</Text>
</View>
) : (
<>
<View style={styles.content}>
<View style={styles.imageContainer}>
<Image
source={{ uri: product.image }}
style={styles.image}
resizeMode="cover"
onError={(e) => console.log('Image error:', e.nativeEvent.error)}
/>
{product.inStock && <Text style={styles.stock}>En Stock</Text>}
</View>
<View style={styles.details}>
<Text style={styles.name}>{product.productName}</Text>
<Text style={styles.blend}>{product.description}</Text>
<View style={styles.quantityContainer}>
<Text style={styles.quantityLabel}>Quantité</Text>
<View style={styles.quantityControls}>
<TouchableOpacity
style={styles.quantityButton}
onPress={() => setQuantity(Math.max(1, quantity - 1))}
>
<Minus size={20} color="#666" />
</TouchableOpacity>
<Text style={styles.quantity}>{quantity}</Text>
<TouchableOpacity
style={styles.quantityButton}
onPress={() => setQuantity(quantity + 1)}
>
<Plus size={20} color="#666" />
</TouchableOpacity>
</View>
</View>
</View>
</View>
<View style={styles.footer}>
<TouchableOpacity style={styles.addButton} onPress={handleAddToCart}>
<Text style={styles.addButtonText}>Ajouter au panier</Text>
</TouchableOpacity>
</View>
</>
)}
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F8F9FA',
},
header: {
flexDirection: 'row',
alignItems: 'center',
padding: 20,
backgroundColor: '#fff',
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
title: {
fontSize: 20,
fontWeight: '600',
color: '#333',
marginLeft: 12,
},
content: {
flex: 1,
padding: 20,
},
imageContainer: {
position: 'relative',
backgroundColor: '#fff',
borderRadius: 16,
padding: 20,
alignItems: 'center',
marginBottom: 24,
},
image: {
width: 200,
height: 300,
borderRadius: 12,
backgroundColor: '#eee', // Fallback while loading
},
stock: {
position: 'absolute',
top: 12,
right: 12,
backgroundColor: '#22C55E',
color: '#fff',
fontSize: 12,
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 4,
},
details: {
backgroundColor: '#fff',
borderRadius: 16,
padding: 20,
},
name: {
fontSize: 24,
fontWeight: '600',
color: '#333',
marginBottom: 8,
},
blend: {
fontSize: 16,
color: '#666',
marginBottom: 24,
},
quantityContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
quantityLabel: {
fontSize: 16,
color: '#333',
},
quantityControls: {
flexDirection: 'row',
alignItems: 'center',
},
quantityButton: {
width: 40,
height: 40,
backgroundColor: '#F1F3F5',
borderRadius: 20,
alignItems: 'center',
justifyContent: 'center',
},
quantity: {
fontSize: 16,
fontWeight: '600',
marginHorizontal: 16,
},
footer: {
padding: 20,
backgroundColor: '#fff',
borderTopWidth: 1,
borderTopColor: '#eee',
},
addButton: {
backgroundColor: '#B07B4F',
borderRadius: 12,
padding: 16,
alignItems: 'center',
},
addButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
loadingText: {
marginTop: 10,
fontSize: 16,
color: '#666',
},
errorContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
errorText: {
fontSize: 16,
color: '#EF4444',
textAlign: 'center',
marginBottom: 20,
},
retryButton: {
backgroundColor: '#B07B4F',
paddingVertical: 10,
paddingHorizontal: 20,
borderRadius: 8,
},
retryButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
});

View File

@ -1,7 +1,9 @@
import { View, Text, StyleSheet, ScrollView, Image,Button } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { View, Text, StyleSheet, Image, } from 'react-native';
import { TouchableOpacity } from 'react-native-gesture-handler';
import { useRouter } from 'expo-router';
import { BellRing } from 'lucide-react-native';
import COLORS from '@/app/constants/colors';
const UserHomeScreen = () => {
const router = useRouter();
@ -13,6 +15,11 @@ const UserHomeScreen = () => {
<Text style={styles.welcome}>Bienvenue,</Text>
<Text style={styles.shopName}>Nom de Café</Text>
</View>
<View style={styles.notification}>
<TouchableOpacity>
<BellRing size={24} color="#000" />
</TouchableOpacity>
</View>
</View>
<Text style={styles.sectionTitle}>Nos Produits</Text>
@ -23,14 +30,14 @@ const UserHomeScreen = () => {
onPress={() => router.push('/screens/user/GrainsScreen')}>
<Image
source={require('../../../assets/images/coffee_cup.jpg')} style={styles.categoryImage}
source={require('../../../assets/images/grains.jpg')} style={styles.categoryImage}
/>
<Text style={styles.categoryTitle}>Grains de café</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.categoryCard}>
<Image
source={require('../../../assets/images/coffee_cup.jpg')} style={styles.categoryImage}
source={require('../../../assets/images/material.jpg')} style={styles.categoryImage}
/>
<Text style={styles.categoryTitle}>Matériels</Text>
</TouchableOpacity>
@ -48,34 +55,50 @@ const styles = StyleSheet.create({
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 20,
padding: 15,
marginTop:26,
borderBottomWidth:0.2,
borderColor:'#999',
},
welcome: {
fontSize: 16,
color: '#666',
},
shopName: {
fontSize: 24,
fontWeight: '600',
color: '#333',
},
avatar: {
width: 40,
height: 40,
borderRadius: 20,
},
sectionTitle: {
fontSize: 20,
fontWeight: '600',
fontWeight: '700',
color: '#333',
marginHorizontal: 20,
marginBottom: 16,
},
notification:{
backgroundColor:COLORS.secondary,
padding:8,
borderRadius:8,
borderColor:'#000',
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 5,
},
shadowOpacity: 0.2,
shadowRadius: 10,
elevation: 2,
},
sectionTitle: {
fontSize: 26,
fontWeight: '900',
color: '#666',
alignSelf:'center',
marginTop:10,
},
categories: {
padding: 20,
flex: 1,
padding: 15,
justifyContent: 'space-evenly',
},
categoryCard: {
backgroundColor: '#fff',
backgroundColor: COLORS.secondary,
borderRadius: 16,
marginBottom: 16,
overflow: 'hidden',
@ -89,13 +112,17 @@ const styles = StyleSheet.create({
elevation: 3,
},
categoryImage: {
width: '100%',
width: '80%',
alignSelf: 'center',
height: 160,
marginTop: 15,
borderRadius:16,
},
categoryTitle: {
fontSize: 16,
fontWeight: '600',
color: '#333',
fontSize: 20,
alignSelf:'center',
fontWeight: '800',
color: '#000',
padding: 16,
},
});

View File

@ -43,7 +43,13 @@ export default function UserLayout() {
<Tabs.Screen
name="GrainsScreen"
options={{
href: null, // 👈 not shown in bottom nav, but still navigable
href: null,
}}
/>
<Tabs.Screen
name="ProductDetailsScreen"
options={{
href: null,
}}
/>

22
app/utils/cartUtils.tsx Normal file
View File

@ -0,0 +1,22 @@
import { CartItem } from '@/app/constants/types';
export const calculateTotal = (items: CartItem[]): number => {
return items.reduce((total, item) => {
return total + item.price * item.quantity;
}, 0);
};
export const updateItemQuantity = (
items: CartItem[],
itemId: string,
newQuantity: number
): CartItem[] => {
// Don't allow negative quantities
if (newQuantity < 0) {
return items;
}
return items.map((item) =>
item.id === itemId ? { ...item, quantity: newQuantity } : item
);
};

Binary file not shown.

Binary file not shown.

BIN
assets/images/grains.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
assets/images/material.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB