Files
vendora/frontend/app/components/FlashSales.jsx
T

280 lines
8.8 KiB
React

// demodata //normal case
// demoData //camel case ex:iAmAGoodBBoy
// demo_data // snake case ex:i_am_a_good_boy
import { View, Text, ScrollView, TouchableOpacity, Image, ActivityIndicator } from 'react-native'
import React, { useEffect, useState, forwardRef } from 'react'
import MasonryList from '@react-native-seoul/masonry-list';
import { widthPercentageToDP as wp, heightPercentageToDP as hp } from 'react-native-responsive-screen';
import { StarIcon } from 'react-native-heroicons/solid';
import { HeartIcon } from 'react-native-heroicons/outline';
import { HeartIcon as SolidHeartIcon } from 'react-native-heroicons/solid';
import Icon from "react-native-vector-icons/FontAwesome"
import { useNavigation } from '@react-navigation/native';
import { getFavorites, toggleFavorite, fetchProducts } from '../api';
import TabBar from '../themes/TabBar'
import { ArrowLeftIcon, ChevronLeftIcon } from 'react-native-heroicons/outline'
const FlashSales = forwardRef((props, ref) => {
const navigation = useNavigation()
const saleEnd = "2024-12-20T23:45:30";
const [endDate, setEndDate] = useState(calenderLastDate(saleEnd))
const [favorites, setFavorites] = useState([]);
const [loading, setLoading] = useState(true);
const [products, setProducts] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
const timer = setInterval(() => {
const remainigTime = calenderLastDate(saleEnd)
if (remainigTime.total <= 0) {
clearInterval(timer)
}
setEndDate(remainigTime)
}, 1000)
return () => clearInterval(timer)
}, [saleEnd])
// Load products and favorites when component mounts
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
const [productsData, favoriteProducts] = await Promise.all([
fetchProducts(),
getFavorites()
]);
setProducts(productsData || []);
setFavorites(favoriteProducts || []);
setError(null);
} catch (error) {
console.error('Error loading data:', error);
setError('Failed to load products');
setProducts([]);
setFavorites([]);
} finally {
setLoading(false);
}
};
loadData();
}, []);
//Category toggle
const subCategory = ["All", "Newest", "Popular", "Mens", "Kids", "Womens"]
const [activeSet, setActiveSet] = useState("All")
const handleSubCategory = (item) => {
setActiveSet(item)
}
const handleFavouriteToggle = async (productId) => {
try {
const product = products.find(p => p.id === productId);
if (!product) {
console.error('Product not found:', productId);
return;
}
const updatedFavorites = await toggleFavorite(product);
setFavorites(updatedFavorites || []);
} catch (error) {
console.error('Error toggling favorite:', error);
}
};
const isProductFavorited = (productId) => {
return favorites.some(fav => fav.id === productId);
};
const navigateToProductDetails = (productId) => {
navigation.navigate('ProductDetails', { id: productId });
};
if (loading) {
return (
<View className="flex-1 justify-center items-center bg-white">
<ActivityIndicator size="large" color="#704f38" />
</View>
);
}
if (error) {
return (
<View className="flex-1 justify-center items-center bg-white">
<Text className="text-red-500 text-lg">{error}</Text>
<TouchableOpacity
onPress={() => {
setLoading(true);
fetchProducts()
.then(data => {
setProducts(data);
setError(null);
})
.catch(err => setError(err.message || 'Failed to fetch products'))
.finally(() => setLoading(false));
}}
className="mt-4 bg-primary py-2 px-4 rounded-lg"
>
<Text className="text-white">Retry</Text>
</TouchableOpacity>
</View>
);
}
return (
<ScrollView ref={ref} className="bg-white">
<TabBar
prefix={""}
title={"Flash Sales"}
titleStyle={{
color: "black",
fontWeight: "bold",
fontSize: 20
}}
prefixStyle={{
backgroundColor: "transparent"
}}
/>
<View className="flex-row justify-between items-center px-1 mx-4 mb-4">
<Text className="text-xl font-semibold">FlashSales</Text>
{
endDate.total >= 0 ? <View className="flex-row items-center gap-2">
<Text className="font-medium text-gray-500">Closing in :</Text>
<View className="bg-btnPrimary h-8 w-8 flex justify-center items-center rounded-lg">
<Text className="text-primary">{endDate.hours}</Text>
</View>
<Text>:</Text>
<View className="bg-btnPrimary h-8 w-8 flex justify-center items-center rounded-lg">
<Text className="text-primary">{endDate.minutes}</Text>
</View>
<Text>:</Text>
<View className="bg-btnPrimary h-8 w-8 flex justify-center items-center rounded-lg">
<Text className="text-primary">{endDate.seconds}</Text>
</View>
</View> : <></>
}
</View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
>
{
subCategory.map((item, index) => {
let isActive = item == activeSet;
return (
<TouchableOpacity
onPress={() => handleSubCategory(item)}
key={index}
className={`
border border-gray-500 px-4 ml-4 mb-4 rounded-full py-2
${isActive ? "bg-primary" : ""}
`}>
<Text className={`font-bold ${isActive ? "text-white" : ""}`}>
{item}
</Text>
</TouchableOpacity>
)
})
}
</ScrollView>
{products.length === 0 ? (
<View className="items-center justify-center py-20">
<Text className="text-lg text-gray-500">No products available</Text>
</View>
) : (
<MasonryList
data={products}
keyExtractor={(item) => item.id.toString()}
numColumns={2}
showsVerticalScrollIndicator={false}
renderItem={({ item, index }) => (
<CardItem
item={item}
index={index}
handleFavouriteToggle={() => handleFavouriteToggle(item.id)}
isFavourite={isProductFavorited(item.id)}
onProductPress={() => navigateToProductDetails(item.id)}
/>
)}
className="mx-4"
/>
)}
</ScrollView>
)
})
const calenderLastDate = (saleEnd) => {
const nowDate = new Date();
const saleEndDate = new Date(saleEnd)
const total = Math.floor(saleEndDate - nowDate)
const days = Math.floor(total / (1000 * 60 * 60 * 24))
const hours = Math.floor(total / (1000 * 60 * 60) % 24)
const minutes = Math.floor(total / (1000 * 60) % 60)
const seconds = Math.floor(total / (1000) % 60)
return {
total: total,
days: Math.max(0, days),
hours: Math.max(0, hours),
minutes: Math.max(0, minutes),
seconds: Math.max(0, seconds),
}
}
const CardItem = ({ item, index, handleFavouriteToggle, isFavourite, onProductPress }) => {
const navigation = useNavigation();
let isEven = item.id % 2 == 0
const getRating = () => {
if (item.rating) {
if (typeof item.rating === 'object' && item.rating.rate !== undefined) {
return item.rating.rate;
}
if (typeof item.rating === 'number') {
return item.rating;
}
}
if (item.average_rating !== undefined) {
return item.average_rating;
}
return 0;
}
return (
<View className={`${isEven ? "pl-4" : " pr-4"} flex-col gap-3 mb-4`}>
<TouchableOpacity onPress={onProductPress} className="relative">
<Image source={{ uri: item.image }} height={hp(20)} className=" object-cover rounded-3xl" />
<TouchableOpacity
onPress={(e) => {
e.stopPropagation();
handleFavouriteToggle();
}}
className="absolute right-4 top-4 bg-white/50 p-2 rounded-full"
>
{isFavourite ? (
<SolidHeartIcon size={20} color="red" />
) : (
<HeartIcon size={20} color="black" />
)}
</TouchableOpacity>
</TouchableOpacity>
<View className="flex-row justify-between px-1">
<Text className={`flex-1`}>{item.name || item.title}</Text>
<View className="flex-row gap-1 ">
<StarIcon size={20} color={"orange"} />
<Text>{getRating()}</Text>
</View>
</View>
<View className="px-1 ">
<Text className="font-medium">Rs {item.price}</Text>
</View>
</View>
)
}
export default FlashSales