143 lines
No EOL
4.7 KiB
TypeScript
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>
|
|
);
|
|
} |