review integration update
This commit is contained in:
parent
c18b014bcc
commit
73595fa2cc
42
app/api/reviews/route.js
Normal file
42
app/api/reviews/route.js
Normal file
@ -0,0 +1,42 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function GET() {
|
||||
const apiKey = "37eb7f83988cfd76ffb5c5af9adc25652efe5607e39997fc7d0e054d690ef25e";
|
||||
const placeId = "ChIJGyfJwKE7K4gRbq-m1ao-eHo";
|
||||
|
||||
let allReviews = [];
|
||||
let nextPageToken = null;
|
||||
|
||||
try {
|
||||
let pageCount = 0;
|
||||
while (pageCount < 3) {
|
||||
pageCount++;
|
||||
const url = `https://serpapi.com/search.json?engine=google_maps_reviews&hl=en&api_key=${apiKey}&place_id=${placeId}${nextPageToken ? `&next_page_token=${nextPageToken}` : ""}`;
|
||||
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
console.error("Pages API: SerpAPI Error:", data.error);
|
||||
break;
|
||||
}
|
||||
|
||||
if (data.reviews && data.reviews.length > 0) {
|
||||
allReviews = [...allReviews, ...data.reviews];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!data.serpapi_pagination || !data.serpapi_pagination.next_page_token) {
|
||||
break;
|
||||
}
|
||||
|
||||
nextPageToken = data.serpapi_pagination.next_page_token;
|
||||
await new Promise((resolve) => setTimeout(resolve, 2500));
|
||||
}
|
||||
|
||||
return NextResponse.json({ reviews: allReviews, total: allReviews.length });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: "Failed to fetch reviews", details: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@ -75,3 +75,89 @@
|
||||
.tab-style-one .nav-link:hover {
|
||||
color: var(--heading-color);
|
||||
}
|
||||
|
||||
.read-more-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #ffc107;
|
||||
padding: 10px 0 0 0;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
text-align: left;
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.read-more-btn:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.testimonial-two-item {
|
||||
min-height: 250px;
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.testimonial-two-item .text {
|
||||
flex-grow: 1;
|
||||
text-align: left !important;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.google-review-header-flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.google-info-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.google-info-right .google-name {
|
||||
text-transform: uppercase;
|
||||
font-weight: 900;
|
||||
font-size: 20px;
|
||||
margin-bottom: 5px !important;
|
||||
color: #fff;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.google-info-right .ratting {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.google-info-right .ratting i {
|
||||
font-size: 18px;
|
||||
margin-right: 3px;
|
||||
text-shadow:
|
||||
-1px -1px 0 #fff,
|
||||
1px -1px 0 #fff,
|
||||
-1px 1px 0 #fff,
|
||||
1px 1px 0 #fff;
|
||||
/* Alternative for smoother outline */
|
||||
-webkit-text-stroke: 1px #fff;
|
||||
}
|
||||
|
||||
.google-info-right .ratting i.text-warning {
|
||||
color: #ffc212 !important;
|
||||
}
|
||||
|
||||
.google-info-right .ratting i.text-white {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.theme-btn-dark {
|
||||
background: #222222 !important;
|
||||
}
|
||||
|
||||
.theme-btn-dark:hover {
|
||||
background: #000000 !important;
|
||||
}
|
||||
@ -1,8 +1,58 @@
|
||||
"use client";
|
||||
import { sliderProps } from "@/utility/sliderProps";
|
||||
import Slider from "react-slick";
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
const Testimonial = () => {
|
||||
const [reviews, setReviews] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [expandedReview, setExpandedReview] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
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("About: Failed to fetch reviews", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
loadReviews();
|
||||
}, []);
|
||||
|
||||
function renderStars(rating) {
|
||||
return [...Array(5)].map((_, i) => (
|
||||
<i key={i} className={`fa fa-star ${i < rating ? "text-warning" : "text-white"}`}></i>
|
||||
));
|
||||
}
|
||||
|
||||
function getReviewText(r) {
|
||||
return r.text || r.description || r.snippet || r.review_text || r.body || r.content || "";
|
||||
}
|
||||
|
||||
function truncateText(text) {
|
||||
return text.length > 200 ? text.substring(0, 200) + "..." : 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();
|
||||
}
|
||||
|
||||
return (
|
||||
<section
|
||||
className="testimonials-two bgc-primary"
|
||||
@ -35,71 +85,86 @@ const Testimonial = () => {
|
||||
<span className="marquee-inner left">review </span>
|
||||
<span className="marquee-inner left">review </span>
|
||||
</span>
|
||||
<Slider
|
||||
{...sliderProps.testimonialsTwoCarousel}
|
||||
className="testimonials-two-carousel"
|
||||
data-aos="fade-up"
|
||||
data-aos-delay={50}
|
||||
data-aos-duration={1500}
|
||||
data-aos-offset={50}
|
||||
>
|
||||
<div className="testimonial-two-item">
|
||||
<div className="ratting">
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
</div>
|
||||
<div className="text">
|
||||
Visited this beautiful restaurant while nearby for a conference. The staff were welcoming from the very start! We started with the Gobi 65 which had light spice but a to. Of flavour. Then, we had the hakka noodles, masala dosa and chettinadu chicken curry which was so tasty. Our Server Gagan recommended it and I’m glad he did. He was fantastic and if we are in town again, we will definitely return!
|
||||
</div>
|
||||
<span className="author">Cam Larocque</span>
|
||||
{/* <span className="designation">Manager</span> */}
|
||||
|
||||
{loading ? (
|
||||
<div className="text-center">
|
||||
<p>Loading reviews...</p>
|
||||
</div>
|
||||
<div className="testimonial-two-item">
|
||||
<div className="ratting">
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
</div>
|
||||
<div className="text">
|
||||
Good to have another new south india cuisine in GTA. The food was amazing, The service was good and fast, thanks for offering the milk burfi for tasting.
|
||||
I have been to their other restaurant locations in GTA and KW area, the service and food was excellent. All the best for this new location. Must visit for guys looking to try south indian Biryani.
|
||||
</div>
|
||||
<span className="author">Sen K</span>
|
||||
{/* <span className="designation">Manager</span> */}
|
||||
</div>
|
||||
<div className="testimonial-two-item">
|
||||
<div className="ratting mb-3">
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
</div>
|
||||
<div className="text">
|
||||
Absolutely incredible experience! This Tamil restaurant is a hidden gem in Canada. The flavors are rich, authentic, and remind me of home. Each dish was perfectly spiced and cooked to perfection. The staff were warm, welcoming, and attentive, making the whole visit even more enjoyable. The ambiance is cozy yet vibrant, and you can tell they take pride in both their food and service. Highly recommend to anyone looking for a true taste of South Indian cuisine!
|
||||
</div>
|
||||
<span className="author">manimala s</span>
|
||||
{/* <span className="designation">Manager</span> */}
|
||||
</div>
|
||||
{/* <div className="testimonial-two-item">
|
||||
<div className="quote">
|
||||
<i className="flaticon-quote" />
|
||||
</div>
|
||||
<div className="text">
|
||||
Renowned for its versatility in the kitchen, Red King Crab can
|
||||
prepared in various ways, from simple steaming or boiling to
|
||||
elaborate preparations such as grilling incorporating weather
|
||||
loving
|
||||
</div>
|
||||
<span className="author">Salvador I. Burton</span>
|
||||
<span className="designation">Manager</span>
|
||||
</div> */}
|
||||
</Slider>
|
||||
) : (
|
||||
<Slider
|
||||
{...sliderProps.testimonialsTwoCarousel}
|
||||
className="testimonials-two-carousel"
|
||||
data-aos="fade-up"
|
||||
data-aos-delay={50}
|
||||
data-aos-duration={1500}
|
||||
data-aos-offset={50}
|
||||
>
|
||||
{reviews.map((r, index) => {
|
||||
const name = r.user?.name || r.author_name || "Customer";
|
||||
const profileImg = getProfileImage(r);
|
||||
const fullText = getReviewText(r);
|
||||
const isExpanded = expandedReview === index;
|
||||
return (
|
||||
<div key={index} className="testimonial-two-item">
|
||||
<div className="google-review-header-flex">
|
||||
<div className="google-avatar" style={{
|
||||
width: '75px',
|
||||
height: '75px',
|
||||
minWidth: '75px',
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
background: '#eee',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
border: '2px solid #fff'
|
||||
}}>
|
||||
{profileImg ? (
|
||||
<img
|
||||
src={profileImg}
|
||||
alt={name}
|
||||
onError={(e) => (e.target.style.display = 'none')}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
) : (
|
||||
<span style={{ fontSize: '24px', fontWeight: 'bold', color: '#555' }}>{getInitials(name)}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="google-info-right">
|
||||
<span className="google-name">{name}</span>
|
||||
<div className="ratting">
|
||||
{renderStars(r.rating)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text">
|
||||
{isExpanded ? fullText : truncateText(fullText)}
|
||||
</div>
|
||||
{fullText.length > 200 && (
|
||||
<button
|
||||
className="read-more-btn"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setExpandedReview(isExpanded ? null : index);
|
||||
}}
|
||||
>
|
||||
{isExpanded ? "Read Less" : "Read More"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Slider>
|
||||
)}
|
||||
|
||||
<div className="new-button d-flex justify-content-center pt-30">
|
||||
<Link
|
||||
legacyBehavior
|
||||
href="https://www.google.com/maps/place/SHIVA+SAKTHI+RESTAURANT/@43.70693,-79.6499035,17z/data=!4m8!3m7!1s0x882b3ba1c0c9271b:0x7a783eaad5a6af6e!8m2!3d43.70693!4d-79.6473286!9m1!1b1!16s%2Fg%2F11wh3sh7p4?entry=ttu&g_ep=EgoyMDI1MTIwOS4wIKXMDSoASAFQAw%3D%3D"
|
||||
>
|
||||
<a target="_blank" className="theme-btn style-three">Review us on Google</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="shape">
|
||||
<img src="/assets/images/shapes/tomato.png" alt="Shape" loading="lazy" />
|
||||
</div>
|
||||
|
||||
@ -1,8 +1,58 @@
|
||||
"use client";
|
||||
import { sliderProps } from "@/utility/sliderProps";
|
||||
import Slider from "react-slick";
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
const Testimonial = () => {
|
||||
const [reviews, setReviews] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [expandedReview, setExpandedReview] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
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("Home: Failed to fetch reviews", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
loadReviews();
|
||||
}, []);
|
||||
|
||||
function renderStars(rating) {
|
||||
return [...Array(5)].map((_, i) => (
|
||||
<i key={i} className={`fa fa-star ${i < rating ? "text-warning" : "text-white"}`}></i>
|
||||
));
|
||||
}
|
||||
|
||||
function getReviewText(r) {
|
||||
return r.text || r.description || r.snippet || r.review_text || r.body || r.content || "";
|
||||
}
|
||||
|
||||
function truncateText(text) {
|
||||
return text.length > 200 ? text.substring(0, 200) + "..." : 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();
|
||||
}
|
||||
|
||||
return (
|
||||
<section
|
||||
className="testimonials-two bgc-primary"
|
||||
@ -35,71 +85,86 @@ const Testimonial = () => {
|
||||
<span className="marquee-inner left">review </span>
|
||||
<span className="marquee-inner left">review </span>
|
||||
</span>
|
||||
<Slider
|
||||
{...sliderProps.testimonialsTwoCarousel}
|
||||
className="testimonials-two-carousel"
|
||||
data-aos="fade-up"
|
||||
data-aos-delay={50}
|
||||
data-aos-duration={1500}
|
||||
data-aos-offset={50}
|
||||
>
|
||||
<div className="testimonial-two-item">
|
||||
<div className="ratting">
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
</div>
|
||||
<div className="text">
|
||||
Visited this beautiful restaurant while nearby for a conference. The staff were welcoming from the very start! We started with the Gobi 65 which had light spice but a to. Of flavour. Then, we had the hakka noodles, masala dosa and chettinadu chicken curry which was so tasty. Our Server Gagan recommended it and I’m glad he did. He was fantastic and if we are in town again, we will definitely return!
|
||||
</div>
|
||||
<span className="author">Cam Larocque</span>
|
||||
{/* <span className="designation">Manager</span> */}
|
||||
|
||||
{loading ? (
|
||||
<div className="text-center">
|
||||
<p>Loading reviews...</p>
|
||||
</div>
|
||||
<div className="testimonial-two-item">
|
||||
<div className="ratting">
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
</div>
|
||||
<div className="text">
|
||||
Good to have another new south india cuisine in GTA. The food was amazing, The service was good and fast, thanks for offering the milk burfi for tasting.
|
||||
I have been to their other restaurant locations in GTA and KW area, the service and food was excellent. All the best for this new location. Must visit for guys looking to try south indian Biryani.
|
||||
</div>
|
||||
<span className="author">Sen K</span>
|
||||
{/* <span className="designation">Manager</span> */}
|
||||
</div>
|
||||
<div className="testimonial-two-item">
|
||||
<div className="ratting mb-3">
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
<i className="fas fa-star text-white" />
|
||||
</div>
|
||||
<div className="text">
|
||||
Absolutely incredible experience! This Tamil restaurant is a hidden gem in Canada. The flavors are rich, authentic, and remind me of home. Each dish was perfectly spiced and cooked to perfection. The staff were warm, welcoming, and attentive, making the whole visit even more enjoyable. The ambiance is cozy yet vibrant, and you can tell they take pride in both their food and service. Highly recommend to anyone looking for a true taste of South Indian cuisine!
|
||||
</div>
|
||||
<span className="author">manimala s</span>
|
||||
{/* <span className="designation">Manager</span> */}
|
||||
</div>
|
||||
{/* <div className="testimonial-two-item">
|
||||
<div className="quote">
|
||||
<i className="flaticon-quote" />
|
||||
</div>
|
||||
<div className="text">
|
||||
Renowned for its versatility in the kitchen, Red King Crab can
|
||||
prepared in various ways, from simple steaming or boiling to
|
||||
elaborate preparations such as grilling incorporating weather
|
||||
loving
|
||||
</div>
|
||||
<span className="author">Salvador I. Burton</span>
|
||||
<span className="designation">Manager</span>
|
||||
</div> */}
|
||||
</Slider>
|
||||
) : (
|
||||
<Slider
|
||||
{...sliderProps.testimonialsTwoCarousel}
|
||||
className="testimonials-two-carousel"
|
||||
data-aos="fade-up"
|
||||
data-aos-delay={50}
|
||||
data-aos-duration={1500}
|
||||
data-aos-offset={50}
|
||||
>
|
||||
{reviews.map((r, index) => {
|
||||
const name = r.user?.name || r.author_name || "Customer";
|
||||
const profileImg = getProfileImage(r);
|
||||
const fullText = getReviewText(r);
|
||||
const isExpanded = expandedReview === index;
|
||||
return (
|
||||
<div key={index} className="testimonial-two-item">
|
||||
<div className="google-review-header-flex">
|
||||
<div className="google-avatar" style={{
|
||||
width: '75px',
|
||||
height: '75px',
|
||||
minWidth: '75px',
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
background: '#eee',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
border: '2px solid #fff'
|
||||
}}>
|
||||
{profileImg ? (
|
||||
<img
|
||||
src={profileImg}
|
||||
alt={name}
|
||||
onError={(e) => (e.target.style.display = 'none')}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
) : (
|
||||
<span style={{ fontSize: '24px', fontWeight: 'bold', color: '#555' }}>{getInitials(name)}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="google-info-right">
|
||||
<span className="google-name">{name}</span>
|
||||
<div className="ratting">
|
||||
{renderStars(r.rating)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text">
|
||||
{isExpanded ? fullText : truncateText(fullText)}
|
||||
</div>
|
||||
{fullText.length > 200 && (
|
||||
<button
|
||||
className="read-more-btn"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setExpandedReview(isExpanded ? null : index);
|
||||
}}
|
||||
>
|
||||
{isExpanded ? "Read Less" : "Read More"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Slider>
|
||||
)}
|
||||
|
||||
<div className="new-button d-flex justify-content-center pt-30">
|
||||
<Link
|
||||
legacyBehavior
|
||||
href="https://www.google.com/maps/place/SHIVA+SAKTHI+RESTAURANT/@43.70693,-79.6499035,17z/data=!4m8!3m7!1s0x882b3ba1c0c9271b:0x7a783eaad5a6af6e!8m2!3d43.70693!4d-79.6473286!9m1!1b1!16s%2Fg%2F11wh3sh7p4?entry=ttu&g_ep=EgoyMDI1MTIwOS4wIKXMDSoASAFQAw%3D%3D"
|
||||
>
|
||||
<a target="_blank" className="theme-btn style-three">Review us on Google</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="shape">
|
||||
<img src="assets/images/shapes/tomato.png" alt="Shape" loading="lazy" />
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user