"use client"; import { useState, useEffect, useMemo, useRef } from "react"; import Image from "next/image"; import Link from "next/link"; import { getImageUrl } from "@/lib/imageUtils"; import { useCaseStudies } from "@/lib/hooks/useCaseStudy"; const Story = () => { const [activeIndex, setActiveIndex] = useState(0); const [activeImageIndex, setActiveImageIndex] = useState(0); const [imagesLoaded, setImagesLoaded] = useState>(new Set()); const sectionRef = useRef(null); const itemsRef = useRef<(HTMLDivElement | null)[]>([]); const imageContainerRef = useRef(null); // Fetch case studies from API with ordering and limit const params = useMemo(() => ({ ordering: 'display_order', page_size: 5 }), []); const { caseStudies, loading, error } = useCaseStudies(params); // Use only API data - no hardcoded fallback const storyData = caseStudies; // Preload all images to prevent blinking useEffect(() => { if (storyData.length === 0) return; const preloadImages = () => { // Mark first image as loaded immediately setImagesLoaded((prev) => new Set(prev).add(0)); storyData.forEach((item, index) => { if (index === 0) return; // Skip first image as it's already marked const imageUrl = item.thumbnail ? getImageUrl(item.thumbnail) : '/images/case/one.png'; const img = new window.Image(); img.crossOrigin = 'anonymous'; img.onload = () => { // Small delay to ensure image is fully decoded setTimeout(() => { setImagesLoaded((prev) => { const newSet = new Set(prev); newSet.add(index); return newSet; }); }, 50); }; img.onerror = () => { // Still mark as loaded to prevent infinite waiting setImagesLoaded((prev) => { const newSet = new Set(prev); newSet.add(index); return newSet; }); }; img.src = imageUrl; }); }; preloadImages(); }, [storyData]); // Update active image when it becomes loaded useEffect(() => { if (imagesLoaded.has(activeIndex) && activeImageIndex !== activeIndex) { setActiveImageIndex(activeIndex); } }, [imagesLoaded, activeIndex, activeImageIndex]); // Log when API data is loaded useEffect(() => { if (error) { console.error('Error loading case studies:', error); } if (caseStudies.length > 0) { console.log('Case studies loaded:', caseStudies.length); } }, [caseStudies, error]); // Handle scroll-based active index update and image positioning useEffect(() => { if (!sectionRef.current || storyData.length === 0) return; const updateActiveItem = () => { const section = sectionRef.current; const imageContainer = imageContainerRef.current; if (!section || !imageContainer) return; // Find which item is most visible let mostVisibleIndex = activeIndex; let maxVisibility = 0; itemsRef.current.forEach((item, index) => { if (!item) return; const rect = item.getBoundingClientRect(); const viewportHeight = window.innerHeight; const itemCenter = rect.top + rect.height / 2; const viewportCenter = viewportHeight / 2; const distanceFromCenter = Math.abs(itemCenter - viewportCenter); const visibility = 1 - (distanceFromCenter / viewportHeight); if (visibility > maxVisibility && rect.top < viewportHeight && rect.bottom > 0) { maxVisibility = visibility; mostVisibleIndex = index; } }); if (mostVisibleIndex !== activeIndex) { setActiveIndex(mostVisibleIndex); // Only switch image if it's loaded if (imagesLoaded.has(mostVisibleIndex) || mostVisibleIndex === 0) { setActiveImageIndex(mostVisibleIndex); } } // Position image container to align with active item const activeItem = itemsRef.current[mostVisibleIndex]; if (activeItem && imageContainer) { const sectionTop = section.offsetTop; const itemTop = activeItem.offsetTop; const itemHeight = activeItem.offsetHeight; const containerHeight = imageContainer.offsetHeight || 400; // Calculate the offset to center the image with the item // Get the parent container to calculate relative position const contentContainer = activeItem.closest('.tp-story__content'); if (contentContainer) { const contentTop = (contentContainer as HTMLElement).offsetTop; const relativeItemTop = itemTop - contentTop; // Align image center with item center const offset = relativeItemTop + (itemHeight / 2) - (containerHeight / 2); // Apply transform to move the image container imageContainer.style.transform = `translateY(${Math.max(0, offset)}px)`; } } }; // Use Intersection Observer as backup const observerOptions = { root: null, rootMargin: '-20% 0px -20% 0px', threshold: [0, 0.25, 0.5, 0.75, 1] }; const observer = new IntersectionObserver((entries) => { let maxRatio = 0; let mostVisibleIndex = activeIndex; entries.forEach((entry) => { const itemIndex = itemsRef.current.indexOf(entry.target as HTMLDivElement); if (itemIndex !== -1 && entry.intersectionRatio > maxRatio) { maxRatio = entry.intersectionRatio; mostVisibleIndex = itemIndex; } }); if (mostVisibleIndex !== activeIndex && maxRatio > 0.1) { setActiveIndex(mostVisibleIndex); // Only switch image if it's loaded if (imagesLoaded.has(mostVisibleIndex) || mostVisibleIndex === 0) { setActiveImageIndex(mostVisibleIndex); } } }, observerOptions); // Observe all story items itemsRef.current.forEach((item) => { if (item) observer.observe(item); }); // Update on scroll window.addEventListener('scroll', updateActiveItem, { passive: true }); updateActiveItem(); // Initial call return () => { window.removeEventListener('scroll', updateActiveItem); observer.disconnect(); }; }, [storyData.length, activeIndex]); const handleMouseEnter = (index: number) => { setActiveIndex(index); // Only switch image if it's loaded, otherwise wait a bit if (imagesLoaded.has(index) || index === 0) { setActiveImageIndex(index); } else { // Wait for image to load before switching const checkLoaded = setInterval(() => { if (imagesLoaded.has(index)) { setActiveImageIndex(index); clearInterval(checkLoaded); } }, 50); // Timeout after 1 second to prevent infinite waiting setTimeout(() => { clearInterval(checkLoaded); setActiveImageIndex(index); }, 1000); } }; // Show loading state if (loading) { return (
Loading...

Loading case studies...

); } // Show error or no data state if (error || !storyData || storyData.length === 0) { return (

Enterprise Case Studies

No data available

); } return (

Enterprise Case Studies

{storyData.map((item, index) => { return (
{ itemsRef.current[index] = el; }} className={`tp-story__single fade-top ${ index === activeIndex ? "active" : "" }`} onMouseEnter={() => handleMouseEnter(index)} >

{item.category_name || item.category?.name || "Case Study"}

{item.title}

{item.excerpt || item.description?.substring(0, 150) + '...'}

); })}
{storyData.map((item, index) => { // Get the image URL using the utility function const imageUrl = item.thumbnail ? getImageUrl(item.thumbnail) : '/images/case/one.png'; const isActive = index === activeImageIndex; const isLoaded = imagesLoaded.has(index); return (
{item.title { if (!isLoaded) { setImagesLoaded((prev) => { const newSet = new Set(prev); newSet.add(index); return newSet; }); } }} />
); })}
); }; export default Story;