freshyo/apps/user-ui/components/BannerCarousel.tsx
2026-01-24 00:13:15 +05:30

143 lines
No EOL
4.7 KiB
TypeScript

import React, { useState, useRef, useEffect } from 'react';
import { View, Dimensions, Image, ScrollView, NativeSyntheticEvent, NativeScrollEvent } from 'react-native';
import { MyTouchableOpacity, MyText, tw } from 'common-ui';
import { useRouter } from 'expo-router';
import { trpc } from '@/src/trpc-client';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
const { width: screenWidth } = Dimensions.get('window');
interface Banner {
id: number;
name: string;
imageUrl: string;
description?: string | null;
productIds?: number[] | null;
redirectUrl?: string | null;
serialNum?: number | null;
isActive: boolean;
}
export default function BannerCarousel() {
const router = useRouter();
const scrollViewRef = useRef<ScrollView>(null);
const [currentIndex, setCurrentIndex] = useState(0);
const [isAutoPlaying, setIsAutoPlaying] = useState(true);
// Fetch banners data
const { data: bannersData, isLoading, error } = trpc.user.banner.getBanners.useQuery();
const banners = bannersData?.banners || [];
// Auto-play functionality
useEffect(() => {
if (banners.length <= 1 || !isAutoPlaying) return; // Don't auto-play if conditions not met
const interval = setInterval(() => {
setCurrentIndex((prevIndex) => {
const nextIndex = (prevIndex + 1) % banners.length;
// Auto-scroll to next slide
if (scrollViewRef.current) {
scrollViewRef.current.scrollTo({
x: nextIndex * (screenWidth - 32),
animated: true,
});
}
return nextIndex;
});
}, 6000); // 6 seconds
return () => clearInterval(interval); // Cleanup on unmount
}, [banners.length, isAutoPlaying]);
if (isLoading) {
return (
<View style={tw`px-4 py-4 bg-gradient-to-r from-pink-500 to-rose-500 items-center justify-center h-48`}>
<MyText style={tw`text-white`}>Loading banners...</MyText>
</View>
);
}
if (error || !banners || banners.length === 0) return null;
const handleBannerPress = (banner: Banner) => {
if (banner.productIds && banner.productIds.length > 0) {
// Navigate to the first product's detail page
router.push(`/(drawer)/(tabs)/home/product-detail/${banner.productIds[0]}`);
} else if (banner.redirectUrl) {
// Handle external URL - could open in browser or handle deep links
console.log('Banner redirect URL:', banner.redirectUrl);
}
// If no productIds or redirectUrl, banner is just for display
};
const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
const slideSize = screenWidth - 32; // width minus horizontal padding
const index = Math.round(event.nativeEvent.contentOffset.x / slideSize);
setCurrentIndex(index);
};
const goToSlide = (index: number) => {
setIsAutoPlaying(false); // Pause auto-play when user manually navigates
if (scrollViewRef.current) {
scrollViewRef.current.scrollTo({
x: index * (screenWidth - 32),
animated: true,
});
}
setCurrentIndex(index);
// Resume auto-play after a short delay
setTimeout(() => setIsAutoPlaying(true), 1000);
};
return (
<View style={tw`px-4 py-4 bg-gradient-to-r from-pink-500 to-rose-500`}>
<ScrollView
ref={scrollViewRef}
horizontal
showsHorizontalScrollIndicator={false}
pagingEnabled
decelerationRate="fast"
snapToInterval={screenWidth - 32}
onScroll={handleScroll}
scrollEventThrottle={16}
onTouchStart={() => setIsAutoPlaying(false)}
onTouchEnd={() => setIsAutoPlaying(true)}
onMomentumScrollEnd={() => setIsAutoPlaying(true)}
>
{banners.map((banner: Banner) => (
<MyTouchableOpacity
key={banner.id}
onPress={() => handleBannerPress(banner)}
style={tw`mr-4 rounded-2xl overflow-hidden`}
activeOpacity={0.9}
>
<Image
source={{ uri: banner.imageUrl }}
style={tw`w-[${screenWidth - 64}px] h-48 rounded-2xl`}
resizeMode="cover"
/>
</MyTouchableOpacity>
))}
</ScrollView>
{/* Pagination Dots */}
{banners.length > 1 && (
<View style={tw`flex-row justify-center mt-3`}>
{banners.map((_, index: number) => (
<MyTouchableOpacity
key={index}
onPress={() => goToSlide(index)}
style={tw`mx-1`}
>
<View
style={tw`w-2 h-2 rounded-full ${
index === currentIndex ? 'bg-gray-800' : 'bg-gray-400'
}`}
/>
</MyTouchableOpacity>
))}
</View>
)}
</View>
);
}