206 lines
7.3 KiB
JavaScript
206 lines
7.3 KiB
JavaScript
'use client'
|
|
import { useState, useEffect } from "react"
|
|
import { Autoplay, Navigation, Pagination } from "swiper/modules"
|
|
import { Swiper, SwiperSlide } from "swiper/react"
|
|
|
|
const swiperOptions = {
|
|
modules: [Autoplay, Pagination, Navigation],
|
|
slidesPerView: 1,
|
|
spaceBetween: 30,
|
|
autoplay: {
|
|
delay: 2500,
|
|
disableOnInteraction: false,
|
|
},
|
|
loop: true,
|
|
|
|
// Navigation
|
|
navigation: {
|
|
nextEl: '.h1n',
|
|
prevEl: '.h1p',
|
|
},
|
|
|
|
// Pagination
|
|
pagination: {
|
|
el: '.swiper-pagination',
|
|
clickable: true,
|
|
},
|
|
|
|
breakpoints: {
|
|
320: {
|
|
slidesPerView: 1,
|
|
// spaceBetween: 30,
|
|
},
|
|
575: {
|
|
slidesPerView: 1,
|
|
// spaceBetween: 30,
|
|
},
|
|
767: {
|
|
slidesPerView: 1,
|
|
// spaceBetween: 30,
|
|
},
|
|
991: {
|
|
slidesPerView: 1,
|
|
// spaceBetween: 30,
|
|
},
|
|
1199: {
|
|
slidesPerView: 1,
|
|
// spaceBetween: 30,
|
|
},
|
|
1350: {
|
|
slidesPerView: 1,
|
|
// spaceBetween: 30,
|
|
},
|
|
}
|
|
}
|
|
export default function TestimonialSlider1() {
|
|
const [reviews, setReviews] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [expandedReview, setExpandedReview] = useState(null);
|
|
const [isClient, setIsClient] = useState(false);
|
|
|
|
useEffect(() => {
|
|
setIsClient(true);
|
|
async function loadReviews() {
|
|
try {
|
|
const res = await fetch("/api/reviews");
|
|
const data = await res.json();
|
|
const cleaned = (data.reviews || []).filter(r =>
|
|
(r.text || r.description || r.snippet || r.review_text || r.body || r.content) &&
|
|
r.rating >= 4
|
|
);
|
|
setReviews(cleaned);
|
|
} catch (error) {
|
|
console.error("Failed to fetch reviews", error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
loadReviews();
|
|
}, []);
|
|
|
|
const displayedReviews = reviews.length > 0 && reviews.length < 3
|
|
? [...reviews, ...reviews, ...reviews]
|
|
: reviews;
|
|
|
|
function renderStars(rating) {
|
|
return [...Array(5)].map((_, i) => (
|
|
<span key={i} className={`fa fa-star ${i < rating ? "text-warning" : ""}`} style={{ marginRight: "2px" }}></span>
|
|
));
|
|
}
|
|
|
|
function getReviewText(r) {
|
|
return r.text || r.description || r.snippet || r.review_text || r.body || r.content || "";
|
|
}
|
|
|
|
function truncateText(text) {
|
|
return text.length > 150 ? text.substring(0, 150) + "..." : text;
|
|
}
|
|
|
|
function getProfileImage(r) {
|
|
const url = r.profile_photo_url || r.author_profile_photo_url || r.user?.thumbnail;
|
|
if (!url) return null;
|
|
return url.startsWith("http") ? url : `https://lh3.googleusercontent.com/${url}`;
|
|
}
|
|
|
|
function getInitials(name) {
|
|
if (!name) return "U";
|
|
return name.split(' ').map(n => n[0]).join('').substring(0, 2).toUpperCase();
|
|
}
|
|
|
|
if (!isClient) return null;
|
|
|
|
if (loading) {
|
|
return <div className="text-center p-5">Loading reviews...</div>;
|
|
}
|
|
|
|
return (
|
|
|
|
<div className="inner-container">
|
|
<Swiper {...swiperOptions} className="single-item-carousel">
|
|
{displayedReviews.map((r, index) => {
|
|
const fullText = getReviewText(r);
|
|
const isExpanded = expandedReview === index;
|
|
const profileImg = getProfileImage(r);
|
|
const name = r.user?.name || r.author_name || "Customer";
|
|
|
|
return (
|
|
<SwiperSlide key={index} className="slide-item">
|
|
<div className="testimonial-block-one">
|
|
<div className="inner-box" style={{
|
|
position: 'relative',
|
|
display: 'block',
|
|
background: '#ffffff45',
|
|
boxShadow: '0px 2px 70px rgba(0, 0, 0, 0.1)',
|
|
borderRadius: '20px',
|
|
padding: '50px'
|
|
}}>
|
|
<div className="google-review-card" style={{ background: 'transparent', border: 'none', boxShadow: 'none' }}>
|
|
<div className="google-review-header d-flex align-items-center mb_15">
|
|
<div className="google-avatar mr_15" style={{ width: '55px', height: '55px', borderRadius: '50%', overflow: 'hidden', flexShrink: 0, background: '#fff' }}>
|
|
{profileImg ? (
|
|
<img
|
|
src={profileImg}
|
|
alt={name}
|
|
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
|
onError={(e) => {
|
|
e.target.onerror = null;
|
|
e.target.style.display = 'none';
|
|
e.target.parentElement.innerHTML = `<div style="width:100%;height:100%;background:#eee;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:bold;color:#333">${getInitials(name)}</div>`;
|
|
}}
|
|
/>
|
|
) : (
|
|
<div style={{ width: '100%', height: '100%', background: '#eee', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '18px', fontWeight: 'bold', color: '#333' }}>
|
|
{getInitials(name)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="google-user-info">
|
|
<h4 className="google-name mb-0" style={{ color: '#fff', fontSize: '18px', fontWeight: '700' }}>{name}</h4>
|
|
<div className="google-stars" style={{ marginTop: '5px' }}>
|
|
{renderStars(r.rating)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="google-text">
|
|
<p style={{ color: '#fff', fontSize: '16px', lineHeight: '1.6' }}>
|
|
{isExpanded ? fullText : truncateText(fullText)}
|
|
</p>
|
|
|
|
{/* Image support if available */}
|
|
{r.images && r.images.length > 0 && (
|
|
<div className="google-review-images d-flex gap-2 mt-3 overflow-auto">
|
|
{r.images.map((img, i) => (
|
|
<img
|
|
key={i}
|
|
src={img.startsWith('http') ? img : `https://lh3.googleusercontent.com/${img}`}
|
|
alt="Review"
|
|
style={{ width: '80px', height: '80px', objectFit: 'cover', borderRadius: '8px' }}
|
|
onError={(e) => (e.target.style.display = 'none')}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
{fullText.length > 150 && (
|
|
<button
|
|
className="read-more-btn"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
setExpandedReview(isExpanded ? null : index);
|
|
}}
|
|
style={{ color: '#fff', border: 'none', background: 'none', padding: '0', cursor: 'pointer', fontWeight: 'bold', display: 'block', marginTop: '10px' }}
|
|
>
|
|
{isExpanded ? "Read Less" : "Read More"}
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</SwiperSlide>
|
|
);
|
|
})}
|
|
</Swiper>
|
|
</div>
|
|
)
|
|
}
|