741 lines
28 KiB
TypeScript
741 lines
28 KiB
TypeScript
import { notFound } from 'next/navigation';
|
|
import { loadImageMapping } from '@/lib/wordpress-content';
|
|
import { generateSEOMetadata, generateStructuredData } from '@/lib/seo';
|
|
import { getPageBySlug, getAllPageSlugs } from '@/lib/wordpress-data-loader';
|
|
import { cleanWordPressContent } from '@/lib/clean-wordpress-content';
|
|
import { getLocationBySlug, getAllLocationSlugs } from '@/lib/location-data';
|
|
import { businessConfig, socialProfiles } from '@/lib/seo-config';
|
|
import { Phone, Mail, Globe, Clock, CreditCard, MapPin } from 'lucide-react';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import { ReviewsSection } from '@/components/reviews-section';
|
|
import { Button } from '@/components/ui/button';
|
|
import Link from 'next/link';
|
|
import type { Metadata } from 'next';
|
|
import React from 'react';
|
|
import { FAQSchema } from '@/components/faq-schema';
|
|
import { FAQSection } from '@/components/faq-section';
|
|
import { ContactPage } from '@/components/contact-page';
|
|
import { AboutPage } from '@/components/about-page';
|
|
import { WhoWeServePage } from '@/components/who-we-serve-page';
|
|
|
|
interface PageProps {
|
|
params: Promise<{ slug: string[] }>;
|
|
}
|
|
|
|
// Route mapping: navigation URLs -> WordPress page slugs
|
|
const routeMapping: Record<string, string> = {
|
|
// Services
|
|
'services/repairs': 'vending-machine-repairs',
|
|
'services/moving': 'vending-machine-repairs', // Placeholder - no moving page exists
|
|
'services/parts': 'parts-and-support',
|
|
'services': 'vending-machine-repairs', // Default to repairs page
|
|
|
|
// Vending Machines
|
|
'vending-machines': 'vending-machines', // Main vending machines page
|
|
'vending-machines/machines-we-use': 'vending-machines', // Use main page
|
|
'vending-machines/machines-for-sale': 'vending-machines-for-sale-in-utah',
|
|
|
|
// Who We Serve
|
|
'warehouses': 'streamlining-snack-and-beverage-access-in-warehouse-environments',
|
|
'auto-repair': 'enhancing-auto-repair-facilities-with-convenient-vending-solutions',
|
|
'gyms': 'vending-machine-for-your-gym',
|
|
'community-centers': 'vending-for-your-community-centers',
|
|
'dance-studios': 'vending-machine-for-your-dance-studio',
|
|
'car-washes': 'vending-machines-for-your-car-wash',
|
|
|
|
// Food & Beverage
|
|
'food-and-beverage/healthy-options': 'healthy-vending',
|
|
'food-and-beverage/traditional-options': 'traditional-vending',
|
|
'food-and-beverage/suppliers': 'diverse-vending-options-with-rocky-mountain-vendings-exclusive-wholesale-accounts',
|
|
|
|
// About
|
|
'about-us': 'about-us',
|
|
'about/faqs': 'faqs',
|
|
};
|
|
|
|
// Helper function to check if a route is a location page
|
|
function isLocationRoute(slugArray: string[]): boolean {
|
|
// Location pages follow pattern: vending-machines-{location}
|
|
// e.g., ["vending-machines-salt-lake-city-utah"] or ["vending-machines", "salt-lake-city-utah"]
|
|
if (slugArray.length === 1) {
|
|
const slug = slugArray[0];
|
|
// Check if it starts with "vending-machines-" and the rest is a valid location slug
|
|
if (slug.startsWith('vending-machines-')) {
|
|
const locationSlug = slug.replace('vending-machines-', '');
|
|
return !!getLocationBySlug(locationSlug);
|
|
}
|
|
} else if (slugArray.length === 2 && slugArray[0] === 'vending-machines') {
|
|
return !!getLocationBySlug(slugArray[1]);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Helper function to resolve route to WordPress slug
|
|
function resolveRouteToSlug(slugArray: string[]): string | null {
|
|
const route = slugArray.join('/');
|
|
|
|
// Check if this is a location page - if so, return null to let Next.js handle it
|
|
// (location pages are handled by vending-machines-[location] route)
|
|
if (isLocationRoute(slugArray)) {
|
|
return null; // Let the location route handle it
|
|
}
|
|
|
|
// Check direct mapping first
|
|
if (routeMapping[route]) {
|
|
return routeMapping[route];
|
|
}
|
|
|
|
// Check if it's a direct WordPress slug
|
|
const directSlug = slugArray.join('-');
|
|
if (getPageBySlug(directSlug)) {
|
|
return directSlug;
|
|
}
|
|
|
|
// Check last segment as fallback (for nested routes)
|
|
if (slugArray.length > 1) {
|
|
const lastSegment = slugArray[slugArray.length - 1];
|
|
if (getPageBySlug(lastSegment)) {
|
|
return lastSegment;
|
|
}
|
|
}
|
|
|
|
// Try the full route as-is
|
|
if (getPageBySlug(route)) {
|
|
return route;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Render location page component
|
|
function renderLocationPage(locationData: any, locationSlug: string) {
|
|
const structuredData = {
|
|
"@context": "https://schema.org",
|
|
"@type": "LocalBusiness",
|
|
name: businessConfig.name,
|
|
description: `Rocky Mountain Vending provides high-quality vending machines, vending machine sales, and vending machine repair services to businesses and schools across ${locationData.city}, ${locationData.state}.`,
|
|
url: `${businessConfig.website}/vending-machines-${locationSlug}`,
|
|
telephone: businessConfig.phoneFormatted,
|
|
priceRange: "$$",
|
|
foundingDate: businessConfig.openingDate,
|
|
areaServed: {
|
|
"@type": "City",
|
|
name: locationData.city,
|
|
address: {
|
|
"@type": "PostalAddress",
|
|
addressLocality: locationData.city,
|
|
addressRegion: locationData.stateAbbr,
|
|
postalCode: locationData.zipCode,
|
|
addressCountry: "US",
|
|
},
|
|
},
|
|
geo: {
|
|
"@type": "GeoCoordinates",
|
|
latitude: locationData.latitude,
|
|
longitude: locationData.longitude,
|
|
},
|
|
openingHoursSpecification: [
|
|
{
|
|
"@type": "OpeningHoursSpecification",
|
|
dayOfWeek: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
|
|
opens: "08:00",
|
|
closes: "17:00",
|
|
},
|
|
],
|
|
paymentAccepted: "Credit Card, Debit Card, American Express, Discover, MasterCard, Visa",
|
|
availableLanguage: ["English"],
|
|
sameAs: [
|
|
socialProfiles.linkedin,
|
|
socialProfiles.facebook,
|
|
socialProfiles.youtube,
|
|
socialProfiles.twitter,
|
|
locationData.chamberUrl,
|
|
locationData.cityWebsite,
|
|
...locationData.localLinks.map((link: any) => link.url),
|
|
],
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} />
|
|
|
|
<article className="container mx-auto px-4 py-8 md:py-12">
|
|
{/* Hero Section */}
|
|
<header className="mb-12 text-center">
|
|
<h1 className="text-4xl md:text-5xl font-bold mb-6">
|
|
Vending Machine Supplier in {locationData.city}, {locationData.state}
|
|
</h1>
|
|
<p className="text-lg md:text-xl text-muted-foreground max-w-3xl mx-auto leading-relaxed">
|
|
Need a vending machine supplier in {locationData.city}, {locationData.state}? Rocky Mountain Vending has
|
|
been helping local businesses and schools since 2019 with quality vending solutions. We bring healthy
|
|
snacks, cold drinks, and dependable service right to your door—no hassle, no fuss.
|
|
</p>
|
|
</header>
|
|
|
|
{/* Local Anecdote */}
|
|
<div className="mb-12 max-w-4xl mx-auto">
|
|
<Card className="border-secondary/20 bg-secondary/5">
|
|
<CardContent className="p-6 md:p-8">
|
|
<p className="text-base md:text-lg leading-relaxed">
|
|
A while back, we worked with a {locationData.anecdote.customer} near {locationData.anecdote.location}.
|
|
We set them up with a {locationData.anecdote.solution}. Now {locationData.anecdote.outcome}. That's
|
|
what we do best—make vending simple.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Services Section */}
|
|
<section className="mb-16 max-w-4xl mx-auto">
|
|
<h2 className="text-3xl font-bold mb-8">{locationData.h2Variants.services}</h2>
|
|
<p className="text-muted-foreground mb-8">
|
|
We handle everything from picking the right machine to keeping it running smoothly. Here's what we offer:
|
|
</p>
|
|
|
|
<div className="grid gap-6 md:grid-cols-2">
|
|
<Card>
|
|
<CardContent className="p-6">
|
|
<h3 className="text-xl font-semibold mb-3">Vending Machine Sales</h3>
|
|
<p className="text-muted-foreground">
|
|
Need a new machine? We've got options that fit your space and budget. Whether you run a school,
|
|
office, or warehouse, we'll help you choose one that works.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardContent className="p-6">
|
|
<h3 className="text-xl font-semibold mb-3">Vending Machine Repair</h3>
|
|
<p className="text-muted-foreground">
|
|
Machines break down—it happens. When yours does, we fix it fast so you're not stuck with an empty
|
|
snack spot.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardContent className="p-6">
|
|
<h3 className="text-xl font-semibold mb-3">Healthy Snack and Beverage Options</h3>
|
|
<p className="text-muted-foreground">
|
|
We stock machines with healthy choices like granola bars, fruit snacks, and sparkling water. Great
|
|
for schools and gyms that want better options.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardContent className="p-6">
|
|
<h3 className="text-xl font-semibold mb-3">Maintenance Services</h3>
|
|
<p className="text-muted-foreground">
|
|
Regular checkups keep machines working right. We handle restocking, cleaning, and small fixes so you
|
|
don't have to think about it.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Coverage Section */}
|
|
<section className="mb-16 max-w-4xl mx-auto">
|
|
<h2 className="text-3xl font-bold mb-6">{locationData.h2Variants.coverage}</h2>
|
|
<div className="prose prose-lg max-w-none">
|
|
<p className="text-muted-foreground mb-4">
|
|
We service all of {locationData.city}, including{" "}
|
|
{locationData.neighborhoods.map((n: string, i: number) => (
|
|
<span key={n}>
|
|
{i > 0 && i === locationData.neighborhoods.length - 1 && ", and "}
|
|
{i > 0 && i < locationData.neighborhoods.length - 1 && ", "}
|
|
{n}
|
|
</span>
|
|
))}
|
|
. We also deliver to nearby cities like{" "}
|
|
{locationData.nearbyCities.map((c: string, i: number) => (
|
|
<span key={c}>
|
|
{i > 0 && i === locationData.nearbyCities.length - 1 && ", and "}
|
|
{i > 0 && i < locationData.nearbyCities.length - 1 && ", "}
|
|
{c}
|
|
</span>
|
|
))}
|
|
—free delivery within 50 miles of {locationData.city}.
|
|
</p>
|
|
|
|
<p className="text-muted-foreground mb-6">
|
|
The{" "}
|
|
<a
|
|
href={locationData.chamberUrl}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-secondary hover:underline font-medium"
|
|
>
|
|
{locationData.chamberName}
|
|
</a>{" "}
|
|
connects us with local businesses, and we're proud to serve this community. Here are some helpful local
|
|
resources:
|
|
</p>
|
|
|
|
<ul className="space-y-2 mb-6">
|
|
{locationData.localLinks.map((link: any) => (
|
|
<li key={link.url}>
|
|
<a
|
|
href={link.url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-secondary hover:underline flex items-center gap-2"
|
|
>
|
|
<MapPin className="h-4 w-4" />
|
|
{link.name}
|
|
</a>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Contact Section */}
|
|
<section className="mb-16 max-w-4xl mx-auto">
|
|
<h2 className="text-3xl font-bold mb-6">{locationData.h2Variants.contact}</h2>
|
|
<p className="text-muted-foreground mb-8">
|
|
We're open Monday through Friday, 8:00 AM to 5:00 PM. Closed on weekends, but you can always reach out by
|
|
phone or text.
|
|
</p>
|
|
|
|
<div className="grid gap-4 md:grid-cols-3 mb-8">
|
|
<Card>
|
|
<CardContent className="p-6 flex items-start gap-4">
|
|
<Phone className="h-6 w-6 text-secondary flex-shrink-0 mt-1" />
|
|
<div>
|
|
<div className="font-semibold mb-1">Phone</div>
|
|
<a href={businessConfig.phoneUrl} className="text-secondary hover:underline">
|
|
{businessConfig.phone}
|
|
</a>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardContent className="p-6 flex items-start gap-4">
|
|
<Mail className="h-6 w-6 text-secondary flex-shrink-0 mt-1" />
|
|
<div>
|
|
<div className="font-semibold mb-1">Text</div>
|
|
<a href={businessConfig.smsUrl} className="text-secondary hover:underline">
|
|
{businessConfig.phone}
|
|
</a>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardContent className="p-6 flex items-start gap-4">
|
|
<Globe className="h-6 w-6 text-secondary flex-shrink-0 mt-1" />
|
|
<div>
|
|
<div className="font-semibold mb-1">Website</div>
|
|
<a
|
|
href={businessConfig.website}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-secondary hover:underline text-sm"
|
|
>
|
|
rockymountainvending.com
|
|
</a>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* CRM Form */}
|
|
<Card className="border-secondary/20">
|
|
<CardContent className="p-8">
|
|
<div className="text-center mb-6">
|
|
<h3 className="text-2xl font-bold mb-2">Get Your Free Vending Machine</h3>
|
|
<p className="text-muted-foreground">
|
|
Fill out the form below and we'll contact you within 24 hours to discuss your needs.
|
|
</p>
|
|
</div>
|
|
<iframe
|
|
src="https://link.sluice-box.io/widget/form/T76mIdPvC5iBwAI2wFPg"
|
|
style={{ width: "100%", height: "650px", border: "none", borderRadius: "4px" }}
|
|
id="inline-T76mIdPvC5iBwAI2wFPg"
|
|
data-layout="{'id':'INLINE'}"
|
|
data-trigger-type="alwaysShow"
|
|
data-trigger-value=""
|
|
data-activation-type="alwaysActivated"
|
|
data-activation-value=""
|
|
data-deactivation-type="neverDeactivate"
|
|
data-deactivation-value=""
|
|
data-form-name="Request Machine Short"
|
|
data-height="638"
|
|
data-layout-iframe-id="inline-T76mIdPvC5iBwAI2wFPg"
|
|
data-form-id="T76mIdPvC5iBwAI2wFPg"
|
|
title="Request Free Vending Machine Form"
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
</section>
|
|
|
|
{/* Payment Options */}
|
|
<section className="mb-16 max-w-4xl mx-auto">
|
|
<h2 className="text-3xl font-bold mb-6">{locationData.h2Variants.payments}</h2>
|
|
<Card>
|
|
<CardContent className="p-6">
|
|
<div className="flex items-start gap-4 mb-4">
|
|
<CreditCard className="h-6 w-6 text-secondary flex-shrink-0 mt-1" />
|
|
<div>
|
|
<div className="font-semibold mb-2">Payment Methods</div>
|
|
<p className="text-muted-foreground">
|
|
We accept credit cards, debit cards, American Express, Discover, MasterCard, and Visa.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-start gap-4">
|
|
<Clock className="h-6 w-6 text-secondary flex-shrink-0 mt-1" />
|
|
<div>
|
|
<div className="font-semibold mb-2">Language</div>
|
|
<p className="text-muted-foreground">
|
|
Our team speaks English, and we're happy to answer any questions you have.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</section>
|
|
|
|
{/* Why Choose Us */}
|
|
<section className="mb-16 max-w-4xl mx-auto">
|
|
<h2 className="text-3xl font-bold mb-6">{locationData.h2Variants.whyChoose}</h2>
|
|
<div className="prose prose-lg max-w-none">
|
|
<p className="text-muted-foreground mb-4">
|
|
Since 2019, we've been serving businesses and schools across Utah County, Salt Lake County, and Davis
|
|
County. We're local, we're fast, and we care about getting it right. About 95% of our {locationData.city}{" "}
|
|
clients stick with us because we show up when we say we will and fix problems quickly. That's based on our
|
|
own records, so we're confident in it.
|
|
</p>
|
|
<p className="text-muted-foreground mb-6">
|
|
We built our machines to handle Utah's weather—cold winters, hot summers, all of it. You won't have to
|
|
worry about breakdowns when the temperature drops.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="text-center">
|
|
<Button asChild size="lg" className="bg-secondary hover:bg-secondary/90">
|
|
<Link href="#contact">Get Your Free Machine Today</Link>
|
|
</Button>
|
|
</div>
|
|
</section>
|
|
</article>
|
|
|
|
{/* Google Reviews Section */}
|
|
<ReviewsSection />
|
|
</>
|
|
);
|
|
}
|
|
|
|
// Generate static params for all pages
|
|
export async function generateStaticParams() {
|
|
try {
|
|
const slugs = getAllPageSlugs();
|
|
const params: Array<{ slug: string[] }> = [];
|
|
|
|
// Add all WordPress page slugs
|
|
slugs.forEach((slug: string) => {
|
|
params.push({
|
|
slug: [slug], // Catch-all routes need arrays
|
|
});
|
|
});
|
|
|
|
// Add mapped routes (like /services, /services/repairs, etc.)
|
|
Object.keys(routeMapping).forEach((route) => {
|
|
const routeArray = route.split('/');
|
|
// Only add if it's not already added as a WordPress slug
|
|
if (!slugs.includes(route)) {
|
|
params.push({
|
|
slug: routeArray,
|
|
});
|
|
}
|
|
});
|
|
|
|
// Add location routes (e.g., /vending-machines-salt-lake-city-utah)
|
|
const locationSlugs = getAllLocationSlugs();
|
|
locationSlugs.forEach((locationSlug: string) => {
|
|
params.push({
|
|
slug: [`vending-machines-${locationSlug}`],
|
|
});
|
|
});
|
|
|
|
return params;
|
|
} catch (error) {
|
|
// Silently return empty array in production
|
|
if (process.env.NODE_ENV === 'development') {
|
|
console.error('Error generating static params:', error);
|
|
}
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Generate metadata for a page
|
|
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
|
try {
|
|
const { slug } = await params;
|
|
const slugArray = Array.isArray(slug) ? slug : [slug];
|
|
|
|
// Handle location routes
|
|
if (isLocationRoute(slugArray)) {
|
|
let locationSlug: string;
|
|
if (slugArray.length === 1) {
|
|
locationSlug = slugArray[0].replace('vending-machines-', '');
|
|
} else {
|
|
locationSlug = slugArray[1];
|
|
}
|
|
|
|
const locationData = getLocationBySlug(locationSlug);
|
|
if (!locationData) {
|
|
return {
|
|
title: 'Location Not Found | Rocky Mountain Vending',
|
|
};
|
|
}
|
|
|
|
const title = `Vending Machine Supplier in ${locationData.city}, ${locationData.stateAbbr} | Rocky Mountain Vending`;
|
|
const description = `Get FREE vending machines for your ${locationData.city} business! Rocky Mountain Vending provides quality vending machine sales, repairs, and service in ${locationData.city}, ${locationData.state}. Call (435) 233-9668.`;
|
|
|
|
return {
|
|
title,
|
|
description,
|
|
keywords: [
|
|
`vending machines ${locationData.city}`,
|
|
`vending machine supplier ${locationData.city}`,
|
|
`free vending machines ${locationData.city}`,
|
|
`vending machine repair ${locationData.city}`,
|
|
`${locationData.city} vending`,
|
|
...locationData.neighborhoods.map((n) => `vending machines ${n}`),
|
|
],
|
|
openGraph: {
|
|
title,
|
|
description,
|
|
url: `${businessConfig.website}/vending-machines-${locationSlug}`,
|
|
type: "website",
|
|
locale: "en_US",
|
|
siteName: businessConfig.name,
|
|
},
|
|
twitter: {
|
|
card: "summary_large_image",
|
|
title,
|
|
description,
|
|
},
|
|
};
|
|
}
|
|
|
|
const pageSlug = resolveRouteToSlug(slugArray);
|
|
|
|
if (!pageSlug) {
|
|
return {
|
|
title: 'Page Not Found | Rocky Mountain Vending',
|
|
};
|
|
}
|
|
|
|
const page = getPageBySlug(pageSlug);
|
|
|
|
if (!page) {
|
|
return {
|
|
title: 'Page Not Found | Rocky Mountain Vending',
|
|
};
|
|
}
|
|
|
|
return generateSEOMetadata({
|
|
title: page.title || 'Page',
|
|
description: page.seoDescription || page.excerpt || '',
|
|
excerpt: page.excerpt,
|
|
date: page.date,
|
|
modified: page.modified,
|
|
image: page.images?.[0]?.localPath,
|
|
});
|
|
} catch (error) {
|
|
// Silently return fallback metadata in production
|
|
if (process.env.NODE_ENV === 'development') {
|
|
console.error('Error generating metadata:', error);
|
|
}
|
|
return {
|
|
title: 'Rocky Mountain Vending',
|
|
description: 'Rocky Mountain Vending provides quality vending machine services in Utah.',
|
|
};
|
|
}
|
|
}
|
|
|
|
export default async function WordPressPage({ params }: PageProps) {
|
|
try {
|
|
const { slug } = await params;
|
|
const slugArray = Array.isArray(slug) ? slug : [slug];
|
|
|
|
// If this is a location route, render the location page
|
|
if (isLocationRoute(slugArray)) {
|
|
let locationSlug: string;
|
|
if (slugArray.length === 1) {
|
|
locationSlug = slugArray[0].replace('vending-machines-', '');
|
|
} else {
|
|
locationSlug = slugArray[1];
|
|
}
|
|
|
|
const locationData = getLocationBySlug(locationSlug);
|
|
if (!locationData) {
|
|
notFound();
|
|
}
|
|
|
|
// Render location page
|
|
return renderLocationPage(locationData, locationSlug);
|
|
}
|
|
|
|
const pageSlug = resolveRouteToSlug(slugArray);
|
|
|
|
if (!pageSlug) {
|
|
notFound();
|
|
}
|
|
|
|
const page = getPageBySlug(pageSlug);
|
|
|
|
if (!page) {
|
|
notFound();
|
|
}
|
|
|
|
// Load image mapping (optional, won't break if it fails)
|
|
let imageMapping: any = {};
|
|
try {
|
|
imageMapping = loadImageMapping();
|
|
} catch (e) {
|
|
// Silently fail - image mapping is optional
|
|
}
|
|
|
|
// Clean and render WordPress content as styled React components
|
|
const content = page.content ? (
|
|
<div className="max-w-none">
|
|
{cleanWordPressContent(String(page.content), {
|
|
imageMapping,
|
|
pageTitle: page.title // Pass page title to avoid duplicate headings
|
|
})}
|
|
</div>
|
|
) : (
|
|
<p className="text-muted-foreground">No content available.</p>
|
|
);
|
|
|
|
// Generate structured data
|
|
let structuredData;
|
|
try {
|
|
structuredData = generateStructuredData({
|
|
title: page.title || 'Page',
|
|
description: page.seoDescription || page.excerpt || '',
|
|
url: page.link || page.urlPath || `https://rockymountainvending.com/${pageSlug}/`,
|
|
datePublished: page.date,
|
|
dateModified: page.modified || page.date,
|
|
type: 'WebPage',
|
|
});
|
|
} catch (e) {
|
|
// Silently use fallback structured data in production
|
|
if (process.env.NODE_ENV === 'development') {
|
|
console.error('Error generating structured data:', e);
|
|
}
|
|
structuredData = {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'WebPage',
|
|
headline: page.title || 'Page',
|
|
description: page.seoDescription || '',
|
|
url: `https://rockymountainvending.com/${pageSlug}/`,
|
|
};
|
|
}
|
|
|
|
// Extract FAQs from content if this is the FAQ page
|
|
const faqs: Array<{ question: string; answer: string }> = [];
|
|
if (pageSlug === 'faqs' && page.content) {
|
|
const contentStr = String(page.content);
|
|
// Extract FAQ items from accordion structure
|
|
const questionMatches = contentStr.matchAll(/<span class="ekit-accordion-title">([^<]+)<\/span>/g);
|
|
// Extract full answer content - match everything inside the card-body div until the closing div
|
|
const answerMatches = contentStr.matchAll(/<div class="elementskit-card-body ekit-accordion--content">([\s\S]*?)<\/div>\s*<\/div>\s*<!-- \.elementskit-card END -->/g);
|
|
|
|
const questions = Array.from(questionMatches).map(m => m[1].trim());
|
|
const answers = Array.from(answerMatches).map(m => {
|
|
// Keep HTML but clean up whitespace
|
|
let answer = m[1].trim();
|
|
// Remove the opening <p> and closing </p> if they wrap everything, but keep other HTML
|
|
// Clean up excessive whitespace but preserve HTML structure
|
|
answer = answer.replace(/\n\s*\n/g, '\n').replace(/>\s+</g, '><').trim();
|
|
return answer;
|
|
});
|
|
|
|
// Match questions with answers
|
|
questions.forEach((question, index) => {
|
|
if (answers[index]) {
|
|
faqs.push({ question, answer: answers[index] });
|
|
}
|
|
});
|
|
}
|
|
|
|
// Check if this is a "Who We Serve" page
|
|
const whoWeServeSlugs = [
|
|
'streamlining-snack-and-beverage-access-in-warehouse-environments',
|
|
'enhancing-auto-repair-facilities-with-convenient-vending-solutions',
|
|
'vending-machine-for-your-gym',
|
|
'vending-for-your-community-centers',
|
|
'vending-machine-for-your-dance-studio',
|
|
'vending-machines-for-your-car-wash',
|
|
];
|
|
const isWhoWeServePage = whoWeServeSlugs.includes(pageSlug);
|
|
|
|
// Debug logging for about-us page
|
|
if (process.env.NODE_ENV === 'development' && pageSlug === 'about-us') {
|
|
console.log('About Us Page Debug:', {
|
|
pageSlug,
|
|
faqsLength: faqs.length,
|
|
shouldRenderAboutPage: faqs.length === 0 && pageSlug === 'about-us'
|
|
});
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
|
|
/>
|
|
{faqs.length > 0 && (
|
|
<>
|
|
<FAQSchema
|
|
faqs={faqs}
|
|
pageUrl={page.link || page.urlPath || `https://rockymountainvending.com/${pageSlug}/`}
|
|
/>
|
|
<FAQSection faqs={faqs} />
|
|
</>
|
|
)}
|
|
{faqs.length === 0 && pageSlug === 'contact-us' && (
|
|
<ContactPage />
|
|
)}
|
|
{faqs.length === 0 && pageSlug === 'about-us' && (
|
|
<AboutPage />
|
|
)}
|
|
{faqs.length === 0 && isWhoWeServePage && (
|
|
<WhoWeServePage title={page.title || 'Page'} content={content} />
|
|
)}
|
|
{faqs.length === 0 && pageSlug !== 'contact-us' && pageSlug !== 'about-us' && !isWhoWeServePage && (
|
|
<article className="container mx-auto px-4 py-8 md:py-12 max-w-4xl">
|
|
<header className="mb-8">
|
|
<h1 className="text-4xl md:text-5xl font-bold mb-6">{page.title || 'Page'}</h1>
|
|
</header>
|
|
{content}
|
|
</article>
|
|
)}
|
|
</>
|
|
);
|
|
} catch (error) {
|
|
// Silently return error fallback in production
|
|
if (process.env.NODE_ENV === 'development') {
|
|
console.error('Error rendering page:', error);
|
|
}
|
|
return (
|
|
<div className="container mx-auto px-4 py-8 md:py-12">
|
|
<h1 className="text-4xl md:text-5xl font-bold mb-4">Error Loading Page</h1>
|
|
<p className="text-destructive">There was an error loading this page. Please try again later.</p>
|
|
{process.env.NODE_ENV === 'development' && (
|
|
<pre className="mt-4 p-4 bg-muted rounded">
|
|
{error instanceof Error ? error.message : String(error)}
|
|
</pre>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|