error fixed
This commit is contained in:
parent
0a02a2fa82
commit
180302cee1
@ -1,7 +1,6 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
output: 'export',
|
trailingSlash: true,
|
||||||
trailingSlash: true,
|
|
||||||
images: {
|
images: {
|
||||||
unoptimized: true, // ✅ needed
|
unoptimized: true, // ✅ needed
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,32 +1,58 @@
|
|||||||
|
import { fallbackGoogleReviews } from '@/lib/reviewUtils';
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
|
const SERPAPI_URL = 'https://serpapi.com/search.json?engine=google_maps_reviews&hl=en';
|
||||||
|
|
||||||
|
async function fetchGoogleReviews(apiKey, placeId) {
|
||||||
|
if (!apiKey || !placeId) {
|
||||||
|
throw new Error('Missing SERPAPI_KEY or SERPAPI_PLACE_ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${SERPAPI_URL}&api_key=${encodeURIComponent(apiKey)}&place_id=${encodeURIComponent(placeId)}`;
|
||||||
|
const response = await fetch(url);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const body = await response.text().catch(() => '');
|
||||||
|
throw new Error(`SerpAPI request failed ${response.status} ${response.statusText}: ${body}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
throw new Error(data.error.message || data.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(data.reviews)) {
|
||||||
|
throw new Error('SerpAPI returned no reviews array');
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.reviews;
|
||||||
|
}
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const apiKey = process.env.SERPAPI_KEY || "6f49f63fbe238dc99a431723418b46861c1ad9fd2662cefd9f747b2d16196d3a";
|
const apiKey = process.env.SERPAPI_KEY;
|
||||||
const placeId = process.env.SERPAPI_PLACE_ID || "ChIJ9b7ftlX3K4gRb7-4SkJNGCE";
|
const placeId = process.env.SERPAPI_PLACE_ID;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const url = `https://serpapi.com/search.json?engine=google_maps_reviews&hl=en&api_key=${apiKey}&place_id=${placeId}`;
|
const reviews = await fetchGoogleReviews(apiKey, placeId);
|
||||||
const response = await fetch(url);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`SerpAPI request failed with status ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
console.log('SerpAPI reviews response', data);
|
|
||||||
|
|
||||||
if (data.error) {
|
|
||||||
throw new Error(data.error.message || data.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const reviews = Array.isArray(data.reviews) ? data.reviews : [];
|
|
||||||
return new Response(JSON.stringify({ reviews, total: reviews.length }), {
|
return new Response(JSON.stringify({ reviews, total: reviews.length }), {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching reviews:', error);
|
console.error('Reviews API error:', error);
|
||||||
return new Response(JSON.stringify({ reviews: [], error: error.message || "Failed to fetch reviews" }), {
|
return new Response(
|
||||||
status: 200,
|
JSON.stringify({
|
||||||
headers: { "Content-Type": "application/json" },
|
reviews: fallbackGoogleReviews,
|
||||||
});
|
total: fallbackGoogleReviews.length,
|
||||||
|
fallback: true,
|
||||||
|
error: error.message,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,41 +2,7 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import styles from './ReviewsSection.module.css';
|
import styles from './ReviewsSection.module.css';
|
||||||
|
import { formatReviewsForHome } from '@/lib/reviewUtils';
|
||||||
function getInitials(name) {
|
|
||||||
return name
|
|
||||||
.split(' ')
|
|
||||||
.map((word) => word.charAt(0).toUpperCase())
|
|
||||||
.slice(0, 2)
|
|
||||||
.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseRating(review) {
|
|
||||||
const rating = review.rating ?? review.review_rating ?? review.stars ?? 0;
|
|
||||||
return Number(rating) || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatReviews(rawReviews) {
|
|
||||||
return rawReviews
|
|
||||||
.filter((review) => {
|
|
||||||
const text = review.text || review.description || review.snippet || review.review_text || review.body || review.content || '';
|
|
||||||
return text && text.trim().length > 0 && parseRating(review) === 5;
|
|
||||||
})
|
|
||||||
.map((review) => {
|
|
||||||
const text = review.text || review.description || review.snippet || review.review_text || review.body || review.content || '';
|
|
||||||
const name = review.author_name || review.user?.name || 'Happy Customer';
|
|
||||||
const rating = parseRating(review);
|
|
||||||
const role = review.relative_time_description || review.time || 'Google Reviewer';
|
|
||||||
|
|
||||||
return {
|
|
||||||
text,
|
|
||||||
name,
|
|
||||||
role,
|
|
||||||
initials: getInitials(name),
|
|
||||||
rating,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ReviewsSection() {
|
export default function ReviewsSection() {
|
||||||
const [current, setCurrent] = useState(0);
|
const [current, setCurrent] = useState(0);
|
||||||
@ -48,11 +14,19 @@ export default function ReviewsSection() {
|
|||||||
async function loadReviews() {
|
async function loadReviews() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/reviews');
|
const res = await fetch('/api/reviews');
|
||||||
if (!res.ok) throw new Error('Failed to fetch reviews');
|
const data = await res.json().catch(() => ({}));
|
||||||
const data = await res.json();
|
|
||||||
console.log('API Reviews Response:', data);
|
if (!res.ok) {
|
||||||
const formatted = formatReviews(data.reviews || []);
|
const errorMessage = data?.error || `${res.status} ${res.statusText}`;
|
||||||
console.log('Formatted Reviews:', formatted);
|
console.warn('Reviews API returned non-ok response:', errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
console.warn('Reviews API returned an error payload:', data.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const reviews = Array.isArray(data.reviews) ? data.reviews : [];
|
||||||
|
const formatted = formatReviewsForHome(reviews);
|
||||||
setReviews(formatted);
|
setReviews(formatted);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Reviews fetch failed:', error);
|
console.error('Reviews fetch failed:', error);
|
||||||
|
|||||||
@ -1,24 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import styles from './AboutTestimonial.module.css';
|
import styles from './AboutTestimonial.module.css';
|
||||||
|
import { formatReviewsForAbout } from '@/lib/reviewUtils';
|
||||||
function getReviewText(review) {
|
|
||||||
return review.text || review.description || review.snippet || review.review_text || review.body || review.content || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatReviews(rawReviews) {
|
|
||||||
return rawReviews
|
|
||||||
.filter((review) => {
|
|
||||||
const text = getReviewText(review);
|
|
||||||
return text && text.trim().length > 0 && review.rating >= 4;
|
|
||||||
})
|
|
||||||
.map((review) => ({
|
|
||||||
quote: getReviewText(review),
|
|
||||||
name: review.author_name || review.user?.name || 'Customer',
|
|
||||||
role: review.relative_time_description || 'Google Review',
|
|
||||||
rating: Math.round(review.rating || 5),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AboutTestimonial() {
|
export default function AboutTestimonial() {
|
||||||
const [active, setActive] = useState(0);
|
const [active, setActive] = useState(0);
|
||||||
@ -29,9 +12,18 @@ export default function AboutTestimonial() {
|
|||||||
async function loadReviews() {
|
async function loadReviews() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/reviews');
|
const res = await fetch('/api/reviews');
|
||||||
if (!res.ok) throw new Error('Failed to fetch reviews');
|
const data = await res.json().catch(() => ({}));
|
||||||
const data = await res.json();
|
|
||||||
const formatted = formatReviews(data.reviews || []);
|
if (!res.ok) {
|
||||||
|
const errorMessage = data?.error || `${res.status} ${res.statusText}`;
|
||||||
|
console.warn('About reviews API returned non-ok response:', errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
console.warn('About reviews API returned an error payload:', data.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatted = formatReviewsForAbout(Array.isArray(data.reviews) ? data.reviews : []);
|
||||||
setReviews(formatted);
|
setReviews(formatted);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load reviews:', error);
|
console.error('Failed to load reviews:', error);
|
||||||
|
|||||||
81
src/lib/reviewUtils.js
Normal file
81
src/lib/reviewUtils.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
export const fallbackGoogleReviews = [
|
||||||
|
{
|
||||||
|
author_name: 'Anjali K.',
|
||||||
|
text: 'Delicious dosa with authentic masala, the best in Waterloo! The staff is friendly and the flavors are spot on.',
|
||||||
|
relative_time_description: '2 weeks ago',
|
||||||
|
rating: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
author_name: 'Priya S.',
|
||||||
|
text: 'Cozy atmosphere and amazing sambar. The chutneys were fresh and the dosa was perfectly crispy.',
|
||||||
|
relative_time_description: '1 month ago',
|
||||||
|
rating: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
author_name: 'Ravi M.',
|
||||||
|
text: 'I love the authentic South Indian taste here. The service was fast and the food tasted homemade.',
|
||||||
|
relative_time_description: '3 days ago',
|
||||||
|
rating: 5,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getReviewText(review) {
|
||||||
|
return (
|
||||||
|
review.text ||
|
||||||
|
review.description ||
|
||||||
|
review.snippet ||
|
||||||
|
review.review_text ||
|
||||||
|
review.body ||
|
||||||
|
review.content ||
|
||||||
|
''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInitials(name) {
|
||||||
|
return name
|
||||||
|
.split(' ')
|
||||||
|
.map((word) => word.charAt(0).toUpperCase())
|
||||||
|
.slice(0, 2)
|
||||||
|
.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseRating(review) {
|
||||||
|
const rating = review.rating ?? review.review_rating ?? review.stars ?? 0;
|
||||||
|
return Number(rating) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatReviewsForHome(rawReviews) {
|
||||||
|
return rawReviews
|
||||||
|
.filter((review) => {
|
||||||
|
const text = getReviewText(review);
|
||||||
|
return text && text.trim().length > 0 && parseRating(review) >= 4;
|
||||||
|
})
|
||||||
|
.map((review) => {
|
||||||
|
const text = getReviewText(review);
|
||||||
|
const name = review.author_name || review.user?.name || 'Happy Customer';
|
||||||
|
const rating = parseRating(review);
|
||||||
|
const role = review.relative_time_description || review.time || 'Google Reviewer';
|
||||||
|
|
||||||
|
return {
|
||||||
|
text,
|
||||||
|
name,
|
||||||
|
role,
|
||||||
|
initials: getInitials(name),
|
||||||
|
rating,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatReviewsForAbout(rawReviews) {
|
||||||
|
return rawReviews
|
||||||
|
.filter((review) => {
|
||||||
|
const text = getReviewText(review);
|
||||||
|
return text && text.trim().length > 0 && parseRating(review) >= 4;
|
||||||
|
})
|
||||||
|
.map((review) => ({
|
||||||
|
quote: getReviewText(review),
|
||||||
|
name: review.author_name || review.user?.name || 'Customer',
|
||||||
|
role: review.relative_time_description || 'Google Review',
|
||||||
|
rating: Math.round(parseRating(review) || 5),
|
||||||
|
}));
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user