google review integration updated
This commit is contained in:
parent
1281329360
commit
86f72c7b0b
@ -3,19 +3,12 @@
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { useState, useEffect } from 'react';
|
||||
import TestimonialSlider1 from '@/components/slider/TestimonialSlider1';
|
||||
import GoogleReviewsBranding from '@/components/GoogleReviewsBranding';
|
||||
|
||||
export default function AboutPage() {
|
||||
const [testIndex, setTestIndex] = useState(0);
|
||||
const [openFaq, setOpenFaq] = useState<number | null>(null);
|
||||
|
||||
const testimonials = [
|
||||
{ quote: "VG Fence consistently delivers high-quality materials on time. Their contractor pricing allows me to stay competitive, and their inventory is unmatched.", author: "Mark S.", role: "Local Fence Contractor" },
|
||||
{ quote: "Their galvanized and black finish railings are top-notch. It's rare to find a supplier that combines durability with such an aesthetic appeal.", author: "Sarah L.", role: "Property Manager" },
|
||||
{ quote: "The team at VG Fence is incredibly knowledgeable. They helped us select the right ornamental fencing for our latest residential development.", author: "David K.", role: "Construction Manager" }
|
||||
];
|
||||
|
||||
const nextTestimonial = () => setTestIndex((prev) => (prev + 1) % testimonials.length);
|
||||
const prevTestimonial = () => setTestIndex((prev) => (prev - 1 + testimonials.length) % testimonials.length);
|
||||
|
||||
const faqs = [
|
||||
{ question: "Do you offer contractor pricing?", answer: "Yes! We provide dedicated contractor accounts with specialized pricing. You need to create an account and verify your business details to unlock these rates." },
|
||||
@ -25,15 +18,6 @@ export default function AboutPage() {
|
||||
{ question: "Can I order custom gate sizes?", answer: "Absolutely. We specialize in custom gate fabrication. You can provide us with your specific dimensions and requirements, and we will manufacture them to fit your project perfectly." },
|
||||
];
|
||||
|
||||
// Auto-slide effect
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
nextTestimonial();
|
||||
}, 5000); // Change slide every 5 seconds
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [testIndex]); // Reset timer when index changes (manual navigation)
|
||||
|
||||
return (
|
||||
<div className="about-page-wrapper">
|
||||
{/* Inner Banner */}
|
||||
@ -109,27 +93,25 @@ export default function AboutPage() {
|
||||
</section> */}
|
||||
|
||||
{/* Testimonial Section (Slider) */}
|
||||
<section className="section testimonials-section">
|
||||
<section className="section testimonials-section" style={{ background: 'var(--navy)', position: 'relative', overflow: 'hidden' }}>
|
||||
<div className="container about-testimonial-container">
|
||||
<div className="section-eyebrow about-testimonial-eyebrow">Testimonials</div>
|
||||
<h2 className="section-h2 about-testimonial-h2">What Our Partners <span>Say.</span></h2>
|
||||
<div className="section-eyebrow about-testimonial-eyebrow">Google Reviews</div>
|
||||
<h2 className="section-h2 about-testimonial-h2">What Our Clients <span>Say.</span></h2>
|
||||
<div style={{ marginBottom: '40px' }}>
|
||||
<GoogleReviewsBranding centered />
|
||||
</div>
|
||||
|
||||
<div className="testimonials-slider">
|
||||
<div className="slider-inner" style={{ transform: `translateX(-${testIndex * 100}%)` }}>
|
||||
{testimonials.map((test, idx) => (
|
||||
<div className="slide" key={idx}>
|
||||
<div className="testimonial-card about-testimonial-card-inner">
|
||||
<div className="testimonial-quote about-testimonial-quote">{test.quote}</div>
|
||||
<div className="testimonial-author about-testimonial-author">{test.author}</div>
|
||||
<div className="testimonial-role about-testimonial-role">{test.role}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="slider-controls">
|
||||
<button className="slider-btn" onClick={prevTestimonial}>←</button>
|
||||
<button className="slider-btn" onClick={nextTestimonial}>→</button>
|
||||
</div>
|
||||
<TestimonialSlider1 />
|
||||
|
||||
<div style={{ marginTop: '40px' }}>
|
||||
<Link
|
||||
href="https://www.google.com/search?sca_esv=6bb65fbe9023ef87&sxsrf=ANbL-n7Ve21NawyEFVl0VCb-UwWxayNm3A:1776516772856&si=AL3DRZEsmMGCryMMFSHJ3StBhOdZ2-6yYkXd_doETEE1OR-qOTPu8cN1oNUaKv4BbC8Z5S1BLRCdnCt5Nb6lKq1rerL_NXx8OrNRKd02CSVf6yuKHtX_YpWGoFVyHFR27ursRTkGKr3U5Uqzch13z9mC5g--Y6I0hA%3D%3D&q=VG+Fence+Products+Reviews&sa=X&ved=2ahUKEwi62LvZuPeTAxU_SGwGHZJiC4QQ0bkNegQIKRAH&cshid=1776516821214282&biw=1366&bih=633&dpr=1"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="btn-primary"
|
||||
>
|
||||
Review us on Google
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
59
app/api/google-reviews/route.ts
Normal file
59
app/api/google-reviews/route.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
// Required for Next.js static export (output: 'export' in next.config.ts)
|
||||
export const dynamic = 'force-static';
|
||||
export const revalidate = 3600; // Re-fetch at most every hour
|
||||
|
||||
export async function GET() {
|
||||
const apiKey = "37eb7f83988cfd76ffb5c5af9adc25652efe5607e39997fc7d0e054d690ef25e";
|
||||
// VG Fence Products, Ayr, ON - data_id from Google Maps
|
||||
const dataId = "0x0:0xd39d64a69ab32c65";
|
||||
|
||||
try {
|
||||
const url = `https://serpapi.com/search.json?engine=google_maps_reviews&hl=en&api_key=${apiKey}&data_id=${dataId}`;
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error(`SerpAPI error! Status: ${response.status}, Body: ${errorText}`);
|
||||
return NextResponse.json({
|
||||
error: "SerpAPI Error",
|
||||
status: response.status,
|
||||
message: errorText
|
||||
}, { status: 500 });
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
console.error("SerpAPI Data Error:", data.error);
|
||||
return NextResponse.json({ error: data.error }, { status: 500 });
|
||||
}
|
||||
|
||||
// Normalize reviews to a consistent shape regardless of SerpAPI field names
|
||||
const rawReviews = data.reviews || [];
|
||||
// Return ALL reviews – let the UI filter if needed
|
||||
const reviews = rawReviews.map((r: any) => ({
|
||||
name: r.user?.name || r.username || "Customer",
|
||||
rating: r.rating || 5,
|
||||
text: r.snippet || r.extracted_snippet?.original || r.description || "",
|
||||
date: r.date || r.iso_date || "",
|
||||
profilePhoto: r.user?.thumbnail || r.user_thumbnail || null,
|
||||
reviewLink: r.link || null,
|
||||
}));
|
||||
|
||||
return NextResponse.json({
|
||||
reviews,
|
||||
total: reviews.length,
|
||||
place: data.place_info || null,
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error("Critical error in /api/google-reviews:", error);
|
||||
return NextResponse.json({
|
||||
error: "Internal Server Error",
|
||||
message: error.message,
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
@ -151,7 +151,7 @@ const ContactClient = () => {
|
||||
</div>
|
||||
|
||||
{/* Map Placeholder */}
|
||||
<div style={{ borderRadius: '12px', overflow: 'hidden', border: '1px solid var(--gray-200)', height: '300px', background: 'var(--white)', position: 'relative' }}>
|
||||
<div style={{ borderRadius: '12px', overflow: 'hidden', border: '1px solid var(--gray-200)', height: '440px', background: 'var(--white)', position: 'relative' }}>
|
||||
<iframe
|
||||
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2903.9575459392213!2d-80.447551023419!3d43.29854497112028!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x882c7f0f0f0f0f0f%3A0x0!2zMTI1IEVhcmwgVGhvbXBzb24gUmQsIEF5ciwgT04gTjBCIDFFMCwgQ2FuYWRh!5e0!3m2!1sen!2sca!4v1713350000000!5m2!1sen!2sca"
|
||||
width="100%"
|
||||
|
||||
60
components/GoogleReviewsBranding.tsx
Normal file
60
components/GoogleReviewsBranding.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
|
||||
interface GoogleReviewsBrandingProps {
|
||||
centered?: boolean;
|
||||
}
|
||||
|
||||
const GoogleReviewsBranding: React.FC<GoogleReviewsBrandingProps> = ({ centered = false }) => {
|
||||
return (
|
||||
<div className="google-reviews-branding" style={{
|
||||
marginTop: '15px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: centered ? 'center' : 'flex-start',
|
||||
gap: '0px'
|
||||
}}>
|
||||
<div style={{
|
||||
fontSize: '35px',
|
||||
fontWeight: '700',
|
||||
lineHeight: '1.2',
|
||||
letterSpacing: '3px',
|
||||
fontFamily: '"Product Sans", "Google Sans", Roboto, Arial, sans-serif',
|
||||
display: 'flex',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
<span style={{ color: '#4285F4' }}>G</span>
|
||||
<span style={{ color: '#EA4335' }}>o</span>
|
||||
<span style={{ color: '#FBBC04' }}>o</span>
|
||||
<span style={{ color: '#4285F4' }}>g</span>
|
||||
<span style={{ color: '#34A853' }}>l</span>
|
||||
<span style={{ color: '#EA4335' }}>e</span>
|
||||
</div>
|
||||
<div style={{
|
||||
fontSize: '16px',
|
||||
fontWeight: '700',
|
||||
lineHeight: '1.2',
|
||||
fontFamily: '"Product Sans", "Google Sans", Roboto, Arial, sans-serif',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '5px',
|
||||
marginTop: '-2px'
|
||||
}}>
|
||||
<span style={{ color: '#FFFFFF' }}>Reviews</span>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
gap: '1px',
|
||||
fontSize: '16px',
|
||||
lineHeight: '1'
|
||||
}}>
|
||||
<span style={{ color: '#FBBC04' }}>★</span>
|
||||
<span style={{ color: '#FBBC04' }}>★</span>
|
||||
<span style={{ color: '#FBBC04' }}>★</span>
|
||||
<span style={{ color: '#FBBC04' }}>★</span>
|
||||
<span style={{ color: '#FBBC04' }}>★</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GoogleReviewsBranding;
|
||||
345
components/slider/TestimonialSlider1.tsx
Normal file
345
components/slider/TestimonialSlider1.tsx
Normal file
@ -0,0 +1,345 @@
|
||||
'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 (
|
||||
<div style={{ display: 'flex', gap: '2px' }}>
|
||||
{[1, 2, 3, 4, 5].map((i) => (
|
||||
<svg
|
||||
key={i}
|
||||
width="16" height="16" viewBox="0 0 24 24"
|
||||
fill={i <= rating ? '#FBBC04' : 'none'}
|
||||
stroke={i <= rating ? '#FBBC04' : 'rgba(255,255,255,0.25)'}
|
||||
strokeWidth="1.5"
|
||||
>
|
||||
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
|
||||
</svg>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<img
|
||||
src={photo}
|
||||
alt={name}
|
||||
onError={() => 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 (
|
||||
<div
|
||||
style={{
|
||||
width: '48px',
|
||||
height: '48px',
|
||||
borderRadius: '50%',
|
||||
background: colors[colorIndex],
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '17px',
|
||||
fontWeight: '700',
|
||||
color: '#fff',
|
||||
flexShrink: 0,
|
||||
border: '2px solid rgba(255,255,255,0.15)',
|
||||
}}
|
||||
>
|
||||
{initials}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Inline Google G SVG
|
||||
function GoogleIcon() {
|
||||
return (
|
||||
<svg width="18" height="18" viewBox="0 0 48 48" style={{ flexShrink: 0 }}>
|
||||
<path fill="#4285F4" d="M44.5 20H24v8.5h11.8C34.7 33.9 30.1 37 24 37c-7.2 0-13-5.8-13-13s5.8-13 13-13c3.1 0 5.9 1.1 8.1 2.9l6.4-6.4C34.6 4.1 29.6 2 24 2 11.8 2 2 11.8 2 24s9.8 22 22 22c11 0 21-8 21-22 0-1.3-.2-2.7-.5-4z" />
|
||||
<path fill="#34A853" d="M6.3 14.7l7 5.1C15 16.1 19.1 13 24 13c3.1 0 5.9 1.1 8.1 2.9l6.4-6.4C34.6 4.1 29.6 2 24 2 16.3 2 9.7 7.4 6.3 14.7z" />
|
||||
<path fill="#FBBC05" d="M24 46c5.9 0 11-2 14.7-5.4l-6.8-5.6C29.9 36.7 27.1 37 24 37c-6.1 0-11.3-4-13.2-9.6l-7 5.4C7.5 40.9 15.2 46 24 46z" />
|
||||
<path fill="#EA4335" d="M44.5 20H24v8.5h11.8c-1 2.8-2.8 5.1-5.2 6.6l6.8 5.6C41.6 37.3 45 31.2 45 24c0-1.3-.2-2.7-.5-4z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default function TestimonialSlider1() {
|
||||
const [reviews, setReviews] = useState<Review[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [expandedIndex, setExpandedIndex] = useState<number | null>(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 (
|
||||
<div style={{ display: 'flex', gap: '24px', justifyContent: 'center', padding: '20px 0' }}>
|
||||
{[0, 1, 2].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
style={{
|
||||
flex: '1',
|
||||
maxWidth: '360px',
|
||||
height: '200px',
|
||||
background: 'rgba(255,255,255,0.06)',
|
||||
borderRadius: '16px',
|
||||
animation: 'pulse 1.5s ease-in-out infinite',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || reviews.length === 0) {
|
||||
return (
|
||||
<div style={{ textAlign: 'center', padding: '40px', color: 'rgba(255,255,255,0.6)', fontSize: '15px' }}>
|
||||
{error || "No reviews available at this time."}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="inner-container" style={{ position: 'relative' }}>
|
||||
<Swiper {...swiperOptions} className="single-item-carousel">
|
||||
{loopedReviews.map((r, index) => {
|
||||
const isExpanded = expandedIndex === index;
|
||||
const hasMore = r.text.length > 140;
|
||||
|
||||
return (
|
||||
<SwiperSlide key={index} className="slide-item">
|
||||
<div
|
||||
style={{
|
||||
background: 'rgba(255,255,255,0.06)',
|
||||
backdropFilter: 'blur(12px)',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
borderRadius: '16px',
|
||||
padding: '28px',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '16px',
|
||||
boxSizing: 'border-box',
|
||||
minHeight: '240px',
|
||||
}}
|
||||
>
|
||||
{/* TOP: Avatar + Name + Google badge */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '14px' }}>
|
||||
<Avatar photo={r.profilePhoto} name={r.name} />
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '4px' }}>
|
||||
<span
|
||||
style={{
|
||||
color: '#fff',
|
||||
fontWeight: '700',
|
||||
fontSize: '15px',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
maxWidth: '150px',
|
||||
display: 'block',
|
||||
}}
|
||||
title={r.name}
|
||||
>
|
||||
{r.name}
|
||||
</span>
|
||||
<GoogleIcon />
|
||||
</div>
|
||||
<StarRating rating={r.rating} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Review text */}
|
||||
<div style={{ flex: 1 }}>
|
||||
{r.text ? (
|
||||
<>
|
||||
<p
|
||||
style={{
|
||||
color: 'rgba(255,255,255,0.82)',
|
||||
fontSize: '14px',
|
||||
lineHeight: '1.65',
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
{isExpanded ? r.text : truncate(r.text)}
|
||||
</p>
|
||||
{hasMore && (
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); setExpandedIndex(isExpanded ? null : index); }}
|
||||
style={{
|
||||
marginTop: '8px',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
color: '#FBBC04',
|
||||
cursor: 'pointer',
|
||||
fontWeight: '600',
|
||||
fontSize: '12px',
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
{isExpanded ? 'Read less ↑' : 'Read more ↓'}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<p style={{ color: 'rgba(255,255,255,0.35)', fontSize: '13px', fontStyle: 'italic', margin: 0 }}>
|
||||
No written review.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* BOTTOM: date + view link */}
|
||||
{/* <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 'auto' }}>
|
||||
{r.date && (
|
||||
<span style={{ color: 'rgba(255,255,255,0.35)', fontSize: '11px' }}>{r.date}</span>
|
||||
)}
|
||||
{r.reviewLink && (
|
||||
<a
|
||||
href={r.reviewLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ color: 'rgba(255,255,255,0.35)', fontSize: '11px', textDecoration: 'none' }}
|
||||
onMouseEnter={(e) => ((e.target as HTMLElement).style.color = '#FBBC04')}
|
||||
onMouseLeave={(e) => ((e.target as HTMLElement).style.color = 'rgba(255,255,255,0.35)')}
|
||||
>
|
||||
View on Google →
|
||||
</a>
|
||||
)}
|
||||
</div> */}
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
);
|
||||
})}
|
||||
</Swiper>
|
||||
|
||||
{/* Nav Buttons */}
|
||||
<div style={{ display: 'flex', gap: '12px', justifyContent: 'center', marginTop: '32px' }}>
|
||||
<button
|
||||
className="slider-btn h1p"
|
||||
style={{
|
||||
width: '46px', height: '46px',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
background: 'rgba(255,255,255,0.07)',
|
||||
color: '#fff',
|
||||
border: '1px solid rgba(255,255,255,0.15)',
|
||||
borderRadius: '50%',
|
||||
cursor: 'pointer',
|
||||
fontSize: '18px',
|
||||
transition: 'background 0.2s',
|
||||
}}
|
||||
onMouseEnter={(e) => ((e.currentTarget as HTMLElement).style.background = 'rgba(255,255,255,0.15)')}
|
||||
onMouseLeave={(e) => ((e.currentTarget as HTMLElement).style.background = 'rgba(255,255,255,0.07)')}
|
||||
>←</button>
|
||||
<button
|
||||
className="slider-btn h1n"
|
||||
style={{
|
||||
width: '46px', height: '46px',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
background: 'rgba(255,255,255,0.07)',
|
||||
color: '#fff',
|
||||
border: '1px solid rgba(255,255,255,0.15)',
|
||||
borderRadius: '50%',
|
||||
cursor: 'pointer',
|
||||
fontSize: '18px',
|
||||
transition: 'background 0.2s',
|
||||
}}
|
||||
onMouseEnter={(e) => ((e.currentTarget as HTMLElement).style.background = 'rgba(255,255,255,0.15)')}
|
||||
onMouseLeave={(e) => ((e.currentTarget as HTMLElement).style.background = 'rgba(255,255,255,0.07)')}
|
||||
>→</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user