'use client' import { useState, useEffect } from "react" import { Autoplay, Navigation, Pagination } from "swiper/modules" import { Swiper, SwiperSlide } from "swiper/react" import 'swiper/css'; import 'swiper/css/navigation'; import 'swiper/css/pagination'; interface Review { name: string; rating: number; text: string; date: string; profilePhoto: string | null; reviewLink: string | null; } const swiperOptions = { modules: [Autoplay, Pagination, Navigation], slidesPerView: 3, spaceBetween: 24, autoplay: { delay: 3500, disableOnInteraction: false, }, loop: true, navigation: { nextEl: '.h1n', prevEl: '.h1p', }, pagination: { el: '.swiper-pagination', clickable: true, }, breakpoints: { 0: { slidesPerView: 1, spaceBetween: 16 }, 640: { slidesPerView: 2, spaceBetween: 20 }, 1024: { slidesPerView: 3, spaceBetween: 24 }, }, } function StarRating({ rating }: { rating: number }) { return (
{[1, 2, 3, 4, 5].map((i) => ( ))}
); } function Avatar({ photo, name }: { photo: string | null; name: string }) { const initials = name .split(' ') .map((n) => n[0]) .join('') .substring(0, 2) .toUpperCase(); const [imgError, setImgError] = useState(false); if (photo && !imgError) { return ( {name} setImgError(true)} style={{ width: '48px', height: '48px', borderRadius: '50%', objectFit: 'cover', border: '2px solid rgba(255,255,255,0.15)', flexShrink: 0, }} /> ); } // fallback: coloured circle with initials const colors = ['#4285F4', '#34A853', '#EA4335', '#FBBC05', '#8E44AD', '#E67E22']; const colorIndex = name.charCodeAt(0) % colors.length; return (
{initials}
); } // Inline Google G SVG function GoogleIcon() { return ( ); } export default function TestimonialSlider1() { const [reviews, setReviews] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [expandedIndex, setExpandedIndex] = useState(null); const [isClient, setIsClient] = useState(false); useEffect(() => { setIsClient(true); async function loadReviews() { try { const res = await fetch("/api/google-reviews/"); if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data.message || `HTTP ${res.status}`); } const data = await res.json(); // Only show 5-star reviews const fiveStars = (data.reviews || []).filter((r: Review) => r.rating === 5); setReviews(fiveStars); } catch (err: any) { console.error("Failed to fetch reviews:", err); setError(err.message || "Could not load reviews."); } finally { setLoading(false); } } loadReviews(); }, []); // Swiper needs at least slidesPerView*2 slides for loop to work correctly const loopedReviews = reviews.length > 0 && reviews.length < 6 ? [...reviews, ...reviews] : reviews; function truncate(text: string, max = 140) { return text.length > max ? text.substring(0, max).trimEnd() + '…' : text; } if (!isClient) return null; if (loading) { return (
{[0, 1, 2].map((i) => (
))}
); } if (error || reviews.length === 0) { return (
{error || "No reviews available at this time."}
); } return (
{loopedReviews.map((r, index) => { const isExpanded = expandedIndex === index; const hasMore = r.text.length > 140; return (
{/* TOP: Avatar + Name + Google badge */}
{r.name}
{/* Review text */}
{r.text ? ( <>

{isExpanded ? r.text : truncate(r.text)}

{hasMore && ( )} ) : (

No written review.

)}
{/* BOTTOM: date + view link */} {/* */}
); })}
{/* Nav Buttons */}
); }