1018 lines
46 KiB
TypeScript
1018 lines
46 KiB
TypeScript
import { notFound } from "next/navigation"
|
|
import Link from "next/link"
|
|
import { loadImageMapping, cleanHtmlEntities } from "@/lib/wordpress-content"
|
|
import { generateRegistryMetadata, generateRegistryStructuredData } from "@/lib/seo"
|
|
import { getPageBySlug } from "@/lib/wordpress-data-loader"
|
|
import { cleanWordPressContent } from "@/lib/clean-wordPress-content"
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Separator } from "@/components/ui/separator"
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
Accordion,
|
|
AccordionContent,
|
|
AccordionItem,
|
|
AccordionTrigger,
|
|
} from "@/components/ui/accordion"
|
|
import {
|
|
CheckCircle2,
|
|
Package,
|
|
Wrench,
|
|
Phone,
|
|
ShoppingCart,
|
|
ArrowRight,
|
|
MapPin,
|
|
Truck,
|
|
Award,
|
|
Users,
|
|
DollarSign,
|
|
Search,
|
|
CreditCard,
|
|
} from "lucide-react"
|
|
import { FAQSection } from "@/components/faq-section"
|
|
import { Breadcrumbs } from "@/components/breadcrumbs"
|
|
import {
|
|
PublicPageHeader,
|
|
PublicSectionHeader,
|
|
PublicSurface,
|
|
} from "@/components/public-surface"
|
|
import type { Metadata } from "next"
|
|
|
|
const WORDPRESS_SLUG = "parts-and-support"
|
|
|
|
export async function generateMetadata(): Promise<Metadata> {
|
|
const page = getPageBySlug(WORDPRESS_SLUG)
|
|
|
|
if (!page) {
|
|
return {
|
|
title: "Vending Machine Parts & Support | Rocky Mountain Vending",
|
|
description:
|
|
"Vending machine parts and support services in Utah. Replacement parts for all major vending machine brands.",
|
|
}
|
|
}
|
|
|
|
return generateRegistryMetadata("parts", {
|
|
date: page.date,
|
|
modified: page.modified,
|
|
image: page.images?.[0]?.localPath,
|
|
})
|
|
}
|
|
|
|
export default async function PartsPage() {
|
|
try {
|
|
const page = getPageBySlug(WORDPRESS_SLUG)
|
|
|
|
if (!page) {
|
|
notFound()
|
|
}
|
|
|
|
let imageMapping: any = {}
|
|
try {
|
|
imageMapping = loadImageMapping()
|
|
} catch (e) {
|
|
imageMapping = {}
|
|
}
|
|
|
|
// Extract FAQs from content (similar to repairs page)
|
|
const faqs: Array<{ question: string; answer: string }> = []
|
|
let contentWithoutFAQs = page.content || ""
|
|
let contentWithoutBrands = ""
|
|
|
|
if (page.content) {
|
|
const contentStr = String(page.content)
|
|
|
|
// Simple FAQ extraction - adjust regex based on content structure
|
|
const questionMatches = contentStr.matchAll(
|
|
/<h[2-4][^>]*>([^<]+?)<\/h[2-4]>/gi
|
|
)
|
|
const potentialAnswers = contentStr
|
|
.split(/<h[2-4][^>]*>/)
|
|
.map((section, index) => {
|
|
if (index > 0 && section.trim()) {
|
|
return section.split(/<h[2-4][^>]*>/)[0].trim()
|
|
}
|
|
return null
|
|
})
|
|
.filter(Boolean)
|
|
|
|
// Basic matching - this may need refinement based on actual content
|
|
const questions = Array.from(questionMatches)
|
|
.map((m) => m[1].trim())
|
|
.filter(
|
|
(q) =>
|
|
q.toLowerCase().includes("?") ||
|
|
q.includes("What") ||
|
|
q.includes("How")
|
|
)
|
|
|
|
questions
|
|
.slice(0, Math.min(questions.length, potentialAnswers.length))
|
|
.forEach((question, index) => {
|
|
if (potentialAnswers[index]) {
|
|
const cleanQuestion = question
|
|
.replace(/'/g, "'")
|
|
.replace(/"/g, '"')
|
|
.trim()
|
|
faqs.push({
|
|
question: cleanQuestion,
|
|
answer: potentialAnswers[index],
|
|
})
|
|
}
|
|
})
|
|
|
|
// Remove FAQ-like sections if found
|
|
if (faqs.length > 0) {
|
|
contentWithoutFAQs = contentStr
|
|
.replace(
|
|
/<h[2-4][^>]*>.*?Questions.*?<\/h[2-4]>([\s\S]*?)(?=<h[2-4]|$)/i,
|
|
""
|
|
)
|
|
.trim()
|
|
}
|
|
|
|
// Remove brands sections from content (we'll show them in accordion instead)
|
|
// Match various patterns for brands sections
|
|
const brandsPatterns = [
|
|
/<h[2-4][^>]*>.*?Compatible.*?Brands.*?<\/h[2-4]>[\s\S]*?(?=<h[2-4]|$)/gi,
|
|
/<h[2-4][^>]*>.*?Vending.*?Machine.*?Brands.*?<\/h[2-4]>[\s\S]*?(?=<h[2-4]|$)/gi,
|
|
/<h[2-4][^>]*>.*?Card.*?Reader.*?Brands.*?<\/h[2-4]>[\s\S]*?(?=<h[2-4]|$)/gi,
|
|
/<h[2-4][^>]*>.*?Bill.*?Validator.*?<\/h[2-4]>[\s\S]*?(?=<h[2-4]|$)/gi,
|
|
/<h[2-4][^>]*>.*?Coin.*?Mechanism.*?<\/h[2-4]>[\s\S]*?(?=<h[2-4]|$)/gi,
|
|
]
|
|
|
|
contentWithoutBrands = contentWithoutFAQs
|
|
brandsPatterns.forEach((pattern) => {
|
|
contentWithoutBrands = contentWithoutBrands.replace(pattern, "").trim()
|
|
})
|
|
|
|
// Remove "Available Parts Include" section (we'll show it in a card)
|
|
// Match various patterns - heading, paragraph, lists, etc. - be very aggressive
|
|
const availablePartsPatterns = [
|
|
// Match heading followed by content until next heading or end
|
|
/<h[1-6][^>]*>.*?Available.*?Parts.*?Include.*?<\/h[1-6]>[\s\S]*?(?=<h[1-6]|$)/gi,
|
|
// Match paragraph with the text
|
|
/<p[^>]*>.*?Available.*?Parts.*?Include.*?<\/p>[\s\S]*?(?=<h[1-6]|$)/gi,
|
|
// Match the text anywhere followed by parts list
|
|
/Available Parts Include:?[\s\S]*?(?=Don.*?see|Compatible|Why Choose|Local Expertise|Fast Delivery|<h[1-6]|$)/gi,
|
|
// Match any div/section containing the parts list items
|
|
/<div[^>]*>[\s\S]*?Bill validators and coin mechanisms[\s\S]*?Card readers for cashless payments[\s\S]*?<\/div>/gi,
|
|
/<section[^>]*>[\s\S]*?Bill validators and coin mechanisms[\s\S]*?Card readers for cashless payments[\s\S]*?<\/section>/gi,
|
|
]
|
|
availablePartsPatterns.forEach((pattern) => {
|
|
contentWithoutBrands = contentWithoutBrands.replace(pattern, "").trim()
|
|
})
|
|
|
|
// Remove individual parts list items if they appear as separate elements
|
|
const partsItems = [
|
|
"Bill validators and coin mechanisms",
|
|
"Card readers for cashless payments",
|
|
"Refrigeration components",
|
|
"Keypads and control boards",
|
|
"Motors and actuators",
|
|
"Vending machine locks and security components",
|
|
"Shelves, trays, and spirals for product dispensing",
|
|
"LED lighting upgrades",
|
|
"Replacement doors and panels",
|
|
"Electrical components",
|
|
]
|
|
|
|
// Remove any list items or paragraphs containing these specific parts
|
|
partsItems.forEach((item) => {
|
|
const itemPattern = new RegExp(
|
|
`<li[^>]*>.*?${item.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}.*?<\/li>`,
|
|
"gi"
|
|
)
|
|
contentWithoutBrands = contentWithoutBrands
|
|
.replace(itemPattern, "")
|
|
.trim()
|
|
const paraPattern = new RegExp(
|
|
`<p[^>]*>.*?${item.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}.*?<\/p>`,
|
|
"gi"
|
|
)
|
|
contentWithoutBrands = contentWithoutBrands
|
|
.replace(paraPattern, "")
|
|
.trim()
|
|
})
|
|
|
|
// Remove "Don't see the part you need?" paragraph if it's standalone
|
|
contentWithoutBrands = contentWithoutBrands
|
|
.replace(
|
|
/<p[^>]*>.*?Don.*?see.*?part.*?need.*?contact.*?us.*?<\/p>/gi,
|
|
""
|
|
)
|
|
.trim()
|
|
|
|
// Remove "Why Choose" section (we have it as a card below)
|
|
const whyChoosePatterns = [
|
|
/<h[1-6][^>]*>.*?Why.*?Choose.*?Rocky.*?Mountain.*?Vending.*?<\/h[1-6]>[\s\S]*?(?=<h[1-6]|$)/gi,
|
|
/<h[1-6][^>]*>.*?Why.*?Choose.*?<\/h[1-6]>[\s\S]*?(?=<h[1-6]|$)/gi,
|
|
/Why Choose Rocky Mountain Vending[\s\S]*?(?=Local Expertise|Fast Delivery|Quality|Expert|Competitive|Custom|<h[1-6]|$)/gi,
|
|
]
|
|
whyChoosePatterns.forEach((pattern) => {
|
|
contentWithoutBrands = contentWithoutBrands.replace(pattern, "").trim()
|
|
})
|
|
|
|
// Remove the benefits list items if they appear
|
|
const benefitsItems = [
|
|
"Local Expertise",
|
|
"Fast Delivery",
|
|
"Quality Assurance",
|
|
"Expert Support",
|
|
"Competitive Pricing",
|
|
"Custom Solutions",
|
|
]
|
|
benefitsItems.forEach((item) => {
|
|
const benefitPattern = new RegExp(
|
|
`<li[^>]*>.*?${item.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}.*?<\/li>`,
|
|
"gi"
|
|
)
|
|
contentWithoutBrands = contentWithoutBrands
|
|
.replace(benefitPattern, "")
|
|
.trim()
|
|
const paraPattern = new RegExp(
|
|
`<p[^>]*>.*?${item.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}.*?<\/p>`,
|
|
"gi"
|
|
)
|
|
contentWithoutBrands = contentWithoutBrands
|
|
.replace(paraPattern, "")
|
|
.trim()
|
|
})
|
|
|
|
// Remove duplicate opening paragraph that matches excerpt/description
|
|
// This removes the intro paragraph that duplicates what's in the hero section
|
|
// Match the exact paragraph with ellipsis entity and variations
|
|
const duplicateIntroPatterns = [
|
|
// Match paragraph with ellipsis entity […]
|
|
/<p[^>]*>.*?Vending Machine Parts.*?Rocky Mountain Vending.*?trusted source.*?high-quality.*?vending machine parts.*?Salt Lake City.*?surrounding areas.*?Whether you need replacement components.*?\[…\].*?<\/p>/gi,
|
|
/<p[^>]*>.*?Vending Machine Parts.*?Rocky Mountain Vending.*?trusted source.*?high-quality.*?vending machine parts.*?Salt Lake City.*?\[…\].*?<\/p>/gi,
|
|
// Match without "Vending Machine Parts" prefix but with same content
|
|
/<p[^>]*>.*?Rocky Mountain Vending.*?trusted source.*?high-quality.*?vending machine parts.*?Salt Lake City.*?surrounding areas.*?Whether you need replacement components.*?\[…\].*?<\/p>/gi,
|
|
/<p[^>]*>.*?Rocky Mountain Vending.*?trusted source.*?high-quality.*?vending machine parts.*?Salt Lake City.*?Whether you need replacement components.*?\[…\].*?<\/p>/gi,
|
|
// Match without ellipsis but with full text
|
|
/<p[^>]*>.*?Vending Machine Parts.*?Rocky Mountain Vending.*?trusted source.*?high-quality.*?vending machine parts.*?Salt Lake City.*?surrounding areas.*?Whether you need replacement components.*?repairs or upgrades.*?we.*?got you covered.*?<\/p>/gi,
|
|
/<p[^>]*>.*?Rocky Mountain Vending.*?trusted source.*?high-quality.*?vending machine parts.*?Salt Lake City.*?surrounding areas.*?Whether you need replacement components.*?repairs or upgrades.*?we.*?got you covered.*?<\/p>/gi,
|
|
// Match with "about us've got you covered" typo
|
|
/<p[^>]*>.*?Rocky Mountain Vending.*?trusted source.*?high-quality.*?vending machine parts.*?Salt Lake City.*?surrounding areas.*?Whether you need replacement components.*?about us.*?got you covered.*?<\/p>/gi,
|
|
// Match with minimal overlap
|
|
/<p[^>]*>.*?Vending Machine Parts.*?Rocky Mountain Vending.*?trusted source.*?Whether you need replacement components.*?<\/p>/gi,
|
|
/<p[^>]*>.*?Rocky Mountain Vending.*?trusted source.*?Whether you need replacement components.*?<\/p>/gi,
|
|
]
|
|
duplicateIntroPatterns.forEach((pattern) => {
|
|
contentWithoutBrands = contentWithoutBrands.replace(pattern, "").trim()
|
|
})
|
|
|
|
// Remove redundant "Vending Machine Parts" heading if it's just a duplicate of the page title
|
|
contentWithoutBrands = contentWithoutBrands
|
|
.replace(
|
|
/<h[1-6][^>]*>.*?Vending Machine Parts.*?<\/h[1-6]>\s*(?=<p[^>]*>.*?Rocky Mountain Vending.*?trusted source)/gi,
|
|
""
|
|
)
|
|
.trim()
|
|
contentWithoutBrands = contentWithoutBrands
|
|
.replace(/<h[1-6][^>]*>.*?Vending Machine Parts.*?<\/h[1-6]>\s*$/gi, "")
|
|
.trim()
|
|
|
|
// Remove image references that are just placeholders or duplicates
|
|
contentWithoutBrands = contentWithoutBrands
|
|
.replace(/<img[^>]*alt=["']Vending machine image["'][^>]*>/gi, "")
|
|
.trim()
|
|
contentWithoutBrands = contentWithoutBrands
|
|
.replace(
|
|
/<figure[^>]*>[\s\S]*?Vending machine image[\s\S]*?<\/figure>/gi,
|
|
""
|
|
)
|
|
.trim()
|
|
contentWithoutBrands = contentWithoutBrands
|
|
.replace(/<div[^>]*>[\s\S]*?Vending machine image[\s\S]*?<\/div>/gi, "")
|
|
.trim()
|
|
|
|
// Remove any remaining empty paragraphs or divs
|
|
contentWithoutBrands = contentWithoutBrands
|
|
.replace(/<p[^>]*>\s*<\/p>/gi, "")
|
|
.trim()
|
|
contentWithoutBrands = contentWithoutBrands
|
|
.replace(/<div[^>]*>\s*<\/div>/gi, "")
|
|
.trim()
|
|
|
|
// Clean up multiple consecutive line breaks
|
|
contentWithoutBrands = contentWithoutBrands
|
|
.replace(/\n\s*\n\s*\n/g, "\n\n")
|
|
.trim()
|
|
|
|
// Remove ellipsis entities and other HTML entities that shouldn't be displayed
|
|
contentWithoutBrands = contentWithoutBrands
|
|
.replace(/\[…\]/gi, "")
|
|
.trim()
|
|
contentWithoutBrands = contentWithoutBrands
|
|
.replace(/…/gi, "")
|
|
.trim()
|
|
|
|
// Remove any remaining HTML tags that are just showing as text (malformed)
|
|
// This handles cases where HTML tags are being displayed as text instead of being rendered
|
|
contentWithoutBrands = contentWithoutBrands
|
|
.replace(/<p>/gi, "")
|
|
.trim()
|
|
contentWithoutBrands = contentWithoutBrands
|
|
.replace(/<\/p>/gi, "")
|
|
.trim()
|
|
contentWithoutBrands = contentWithoutBrands
|
|
.replace(/<\/?[^&]+>/gi, "")
|
|
.trim()
|
|
} else {
|
|
contentWithoutBrands = contentWithoutFAQs
|
|
}
|
|
|
|
// Only show content if there's meaningful content (more than just whitespace and minimal text)
|
|
const hasMeaningfulContent =
|
|
contentWithoutBrands &&
|
|
contentWithoutBrands.trim().length > 100 &&
|
|
!contentWithoutBrands.match(/^[\s\n\r]*$/)
|
|
|
|
const content = hasMeaningfulContent ? (
|
|
<div className="max-w-none">
|
|
{cleanWordPressContent(String(contentWithoutBrands), {
|
|
imageMapping,
|
|
pageTitle: page.title,
|
|
})}
|
|
</div>
|
|
) : null
|
|
|
|
const structuredData = generateRegistryStructuredData("parts", {
|
|
datePublished: page.date,
|
|
dateModified: page.modified || page.date,
|
|
})
|
|
|
|
const cleanExcerpt = page.excerpt
|
|
? cleanHtmlEntities(page.excerpt)
|
|
.replace(/…/g, "...")
|
|
.replace(/\[…\]/g, "...")
|
|
.replace(/\[\.\.\.\]/g, "...")
|
|
.replace(/\[\.\.\./g, "...")
|
|
.replace(/\.\.\.\]/g, "...")
|
|
.replace(/\[\.\.\./g, "...")
|
|
.replace(/\[\.\./g, "...")
|
|
.replace(/\.\.\]/g, "...")
|
|
.trim()
|
|
: ""
|
|
|
|
const surfaceCardClass =
|
|
"rounded-[var(--public-surface-radius)] border border-border/70 bg-[linear-gradient(180deg,rgba(255,255,255,0.98),rgba(255,251,243,0.96))] shadow-[var(--public-surface-shadow)]"
|
|
const insetCardClass =
|
|
"rounded-[var(--public-inset-radius)] border border-border/60 bg-white/95 shadow-[0_10px_28px_rgba(15,23,42,0.06)]"
|
|
|
|
return (
|
|
<>
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
|
|
/>
|
|
{/* Hero Section */}
|
|
<section className="py-12 md:py-16 bg-background">
|
|
<div className="container mx-auto px-4 max-w-4xl">
|
|
<Breadcrumbs
|
|
className="mb-6"
|
|
items={[
|
|
{ label: "Services", href: "/services" },
|
|
{ label: "Parts", href: "/services/parts" },
|
|
]}
|
|
/>
|
|
<PublicPageHeader
|
|
align="center"
|
|
eyebrow="Parts & Support"
|
|
title={page.title || "Vending Machine Parts & Support"}
|
|
description={
|
|
cleanExcerpt ||
|
|
"Replacement parts, documentation, and support for the brands and payment systems Rocky Mountain Vending works with most."
|
|
}
|
|
/>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Main Content - Only show if there's meaningful content beyond the intro */}
|
|
{content && (
|
|
<section className="py-12 md:py-16 bg-muted/30">
|
|
<div className="container mx-auto px-4 max-w-4xl">
|
|
<PublicSurface as="article">
|
|
<div className="prose prose-lg max-w-none text-pretty leading-relaxed prose-headings:text-foreground prose-p:text-muted-foreground prose-a:text-foreground prose-a:hover:text-secondary prose-a:transition-colors prose-headings:font-bold prose-headings:tracking-tight prose-img:rounded-lg prose-img:shadow-md">
|
|
{content}
|
|
</div>
|
|
</PublicSurface>
|
|
</div>
|
|
</section>
|
|
)}
|
|
|
|
{content && <Separator className="my-0" />}
|
|
|
|
{/* Services Section */}
|
|
<section className="py-12 md:py-16">
|
|
<div className="container mx-auto px-4 max-w-6xl">
|
|
<PublicSectionHeader
|
|
eyebrow="Service Scope"
|
|
title="Parts & support services"
|
|
description="Comprehensive parts and technical support to keep your vending machines operational."
|
|
className="mx-auto mb-8 max-w-3xl text-center md:mb-12"
|
|
/>
|
|
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
<Card className={`${insetCardClass} h-full hover:border-primary/30`}>
|
|
<CardContent className="p-6">
|
|
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
|
|
<Package className="h-6 w-6 text-primary" />
|
|
</div>
|
|
<h3 className="text-xl font-semibold mb-2">
|
|
Replacement Parts
|
|
</h3>
|
|
<p className="text-muted-foreground text-sm leading-relaxed">
|
|
Replacement parts for all major vending machine brands
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className={`${insetCardClass} h-full hover:border-primary/30`}>
|
|
<CardContent className="p-6">
|
|
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
|
|
<ShoppingCart className="h-6 w-6 text-primary" />
|
|
</div>
|
|
<h3 className="text-xl font-semibold mb-2">
|
|
Payment Components
|
|
</h3>
|
|
<p className="text-muted-foreground text-sm leading-relaxed">
|
|
Coin and bill mechanism components for reliable transactions
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className={`${insetCardClass} h-full hover:border-primary/30`}>
|
|
<CardContent className="p-6">
|
|
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
|
|
<Wrench className="h-6 w-6 text-primary" />
|
|
</div>
|
|
<h3 className="text-xl font-semibold mb-2">
|
|
Cooling Systems
|
|
</h3>
|
|
<p className="text-muted-foreground text-sm leading-relaxed">
|
|
Refrigeration and cooling system parts to keep products
|
|
fresh
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className={`${insetCardClass} h-full hover:border-primary/30`}>
|
|
<CardContent className="p-6">
|
|
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
|
|
<CreditCard className="h-6 w-6 text-primary" />
|
|
</div>
|
|
<h3 className="text-xl font-semibold mb-2">
|
|
Payment Upgrades
|
|
</h3>
|
|
<p className="text-muted-foreground text-sm leading-relaxed">
|
|
Card readers and payment system upgrades for modern
|
|
convenience
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className={`${insetCardClass} h-full hover:border-primary/30`}>
|
|
<CardContent className="p-6">
|
|
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
|
|
<Wrench className="h-6 w-6 text-primary" />
|
|
</div>
|
|
<h3 className="text-xl font-semibold mb-2">
|
|
Electrical Components
|
|
</h3>
|
|
<p className="text-muted-foreground text-sm leading-relaxed">
|
|
Electrical and wiring components for reliable machine
|
|
operation
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className={`${insetCardClass} h-full hover:border-primary/30`}>
|
|
<CardContent className="p-6">
|
|
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
|
|
<Package className="h-6 w-6 text-primary" />
|
|
</div>
|
|
<h3 className="text-xl font-semibold mb-2">
|
|
Dispensing Systems
|
|
</h3>
|
|
<p className="text-muted-foreground text-sm leading-relaxed">
|
|
Vending coils, motors, and dispensers for smooth product
|
|
delivery
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Available Parts Section */}
|
|
<section className="py-12 md:py-16 bg-muted/30">
|
|
<div className="container mx-auto px-4 max-w-6xl">
|
|
<PublicSectionHeader
|
|
eyebrow="Inventory"
|
|
title="Available parts include"
|
|
description="We stock a comprehensive inventory of vending machine parts to keep your equipment running smoothly."
|
|
className="mx-auto mb-8 max-w-3xl text-center md:mb-12"
|
|
/>
|
|
<Card className={surfaceCardClass}>
|
|
<CardHeader>
|
|
<CardTitle className="text-2xl md:text-3xl">
|
|
Parts Inventory
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="px-6 pb-6 pt-2">
|
|
<div className="grid gap-4 md:grid-cols-2">
|
|
<ul className="space-y-3">
|
|
<li className="flex items-start gap-3">
|
|
<CheckCircle2 className="w-5 h-5 text-primary flex-shrink-0 mt-0.5" />
|
|
<span className="text-foreground">
|
|
Bill validators and coin mechanisms
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-3">
|
|
<CheckCircle2 className="w-5 h-5 text-primary flex-shrink-0 mt-0.5" />
|
|
<span className="text-foreground">
|
|
Card readers for cashless payments
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-3">
|
|
<CheckCircle2 className="w-5 h-5 text-primary flex-shrink-0 mt-0.5" />
|
|
<span className="text-foreground">
|
|
Refrigeration components (compressors, evaporators, and
|
|
thermostats)
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-3">
|
|
<CheckCircle2 className="w-5 h-5 text-primary flex-shrink-0 mt-0.5" />
|
|
<span className="text-foreground">
|
|
Keypads and control boards
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-3">
|
|
<CheckCircle2 className="w-5 h-5 text-primary flex-shrink-0 mt-0.5" />
|
|
<span className="text-foreground">
|
|
Motors and actuators
|
|
</span>
|
|
</li>
|
|
</ul>
|
|
<ul className="space-y-3">
|
|
<li className="flex items-start gap-3">
|
|
<CheckCircle2 className="w-5 h-5 text-primary flex-shrink-0 mt-0.5" />
|
|
<span className="text-foreground">
|
|
Vending machine locks and security components
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-3">
|
|
<CheckCircle2 className="w-5 h-5 text-primary flex-shrink-0 mt-0.5" />
|
|
<span className="text-foreground">
|
|
Shelves, trays, and spirals for product dispensing
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-3">
|
|
<CheckCircle2 className="w-5 h-5 text-primary flex-shrink-0 mt-0.5" />
|
|
<span className="text-foreground">
|
|
LED lighting upgrades
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-3">
|
|
<CheckCircle2 className="w-5 h-5 text-primary flex-shrink-0 mt-0.5" />
|
|
<span className="text-foreground">
|
|
Replacement doors and panels
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-3">
|
|
<CheckCircle2 className="w-5 h-5 text-primary flex-shrink-0 mt-0.5" />
|
|
<span className="text-foreground">
|
|
Electrical components (fuses, wiring harnesses, and
|
|
switches)
|
|
</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div className="mt-8 pt-6 border-t border-border/50">
|
|
<p className="text-muted-foreground leading-relaxed text-center">
|
|
Don't see the part you need?{" "}
|
|
<Link
|
|
href="/contact-us"
|
|
className="text-primary hover:text-secondary font-medium transition-colors"
|
|
>
|
|
Contact us
|
|
</Link>
|
|
! We can source specialty parts or recommend alternatives
|
|
for your specific vending machine model.
|
|
</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Compatible Brands Section */}
|
|
<section className="py-12 md:py-16 bg-background">
|
|
<div className="container mx-auto px-4 max-w-6xl">
|
|
<div className="text-center mb-8 md:mb-12">
|
|
<h2 className="text-3xl font-bold tracking-tight md:text-4xl lg:text-5xl mb-4 text-balance">
|
|
Compatible Vending Machine Brands
|
|
</h2>
|
|
<p className="text-lg text-muted-foreground max-w-2xl mx-auto text-pretty leading-relaxed">
|
|
We carry parts for the same major vending machine brands we
|
|
service, ensuring seamless compatibility and performance. If
|
|
your brand isn't listed, get in touch—we may still have the
|
|
parts you need or can source them for you.
|
|
</p>
|
|
</div>
|
|
<Accordion type="single" collapsible className="w-full space-y-4">
|
|
<AccordionItem
|
|
value="vending-brands"
|
|
className={`${insetCardClass} rounded-[1.5rem] px-6 py-2 transition-all hover:border-primary/30`}
|
|
>
|
|
<AccordionTrigger className="text-xl font-semibold hover:no-underline py-4">
|
|
Vending Machine Brands
|
|
</AccordionTrigger>
|
|
<AccordionContent className="pt-0 pb-6">
|
|
<div className="grid gap-3 md:grid-cols-2">
|
|
<ul className="space-y-2">
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">
|
|
AMS (Automated Merchandising Systems)
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">Dixie-Narco</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">Royal Vendors</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">Vendo</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">
|
|
Crane National Vendors
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">Seaga</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">
|
|
USI (United Standard Industries)
|
|
</span>
|
|
</li>
|
|
</ul>
|
|
<ul className="space-y-2">
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">
|
|
HealthyYOU Vending
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">
|
|
AP (Automatic Products)
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">
|
|
National Vendors
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">
|
|
CPI (Crane Payment Innovations)
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">
|
|
Quick Fresh Vending
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">
|
|
GPL (General Products Limited)
|
|
</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
|
|
<AccordionItem
|
|
value="card-reader-brands"
|
|
className={`${insetCardClass} rounded-[1.5rem] px-6 py-2 transition-all hover:border-primary/30`}
|
|
>
|
|
<AccordionTrigger className="text-xl font-semibold hover:no-underline py-4">
|
|
Compatible Card Reader Brands
|
|
</AccordionTrigger>
|
|
<AccordionContent className="pt-0 pb-6">
|
|
<p className="text-foreground mb-4 leading-relaxed">
|
|
Upgrade or replace your cashless payment systems with our
|
|
reliable card reader parts:
|
|
</p>
|
|
<ul className="space-y-2">
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">Nayax</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">
|
|
USA Technologies/Cantaloupe
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">
|
|
Parlevel/365 Markets
|
|
</span>
|
|
</li>
|
|
</ul>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
|
|
<AccordionItem
|
|
value="payment-brands"
|
|
className={`${insetCardClass} rounded-[1.5rem] px-6 py-2 transition-all hover:border-primary/30`}
|
|
>
|
|
<AccordionTrigger className="text-xl font-semibold hover:no-underline py-4">
|
|
Compatible Bill Validator and Coin Mechanism Brands
|
|
</AccordionTrigger>
|
|
<AccordionContent className="pt-0 pb-6">
|
|
<p className="text-foreground mb-4 leading-relaxed">
|
|
Keep your payment systems operational with our high-quality
|
|
bill validator and coin mechanism parts:
|
|
</p>
|
|
<ul className="space-y-2">
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">
|
|
MEI (Mars Electronics International)
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">
|
|
Coinco (Coin Acceptors Inc.)
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">Conlux</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">
|
|
ICT (Innovative Concepts in Technology)
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2 className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
|
<span className="text-foreground">Currenza</span>
|
|
</li>
|
|
</ul>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
</Accordion>
|
|
</div>
|
|
</section>
|
|
|
|
{/* How to Order Parts */}
|
|
<section className="py-12 md:py-16 bg-background">
|
|
<div className="container mx-auto px-4 max-w-6xl">
|
|
<div className="text-center mb-8 md:mb-12">
|
|
<h2 className="text-3xl font-bold tracking-tight md:text-4xl lg:text-5xl mb-4 text-balance">
|
|
How to Order Parts
|
|
</h2>
|
|
<p className="text-lg text-muted-foreground max-w-2xl mx-auto text-pretty leading-relaxed">
|
|
Simple steps to get the parts you need quickly and efficiently
|
|
</p>
|
|
</div>
|
|
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
|
|
<Card className={`${insetCardClass} h-full hover:border-primary/30`}>
|
|
<CardContent className="pt-8">
|
|
<div className="mb-6">
|
|
<div className="inline-flex items-center justify-center h-16 w-16 rounded-full bg-primary/10 text-primary text-2xl font-bold">
|
|
<Search className="h-8 w-8" />
|
|
</div>
|
|
</div>
|
|
<h3 className="text-xl font-semibold mb-3">
|
|
Identify Your Part
|
|
</h3>
|
|
<p className="text-muted-foreground text-sm leading-relaxed">
|
|
Use your machine's manual or contact us with model details
|
|
to identify the exact part number you need.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className={`${insetCardClass} h-full hover:border-primary/30`}>
|
|
<CardContent className="pt-8">
|
|
<div className="mb-6">
|
|
<div className="inline-flex items-center justify-center h-16 w-16 rounded-full bg-primary/10 text-primary">
|
|
<Phone className="h-8 w-8" />
|
|
</div>
|
|
</div>
|
|
<h3 className="text-xl font-semibold mb-3">
|
|
Contact Our Team
|
|
</h3>
|
|
<p className="text-muted-foreground text-sm leading-relaxed">
|
|
Reach out via phone, email, or our contact form. We'll
|
|
confirm availability and provide a quote.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className={`${insetCardClass} h-full hover:border-primary/30`}>
|
|
<CardContent className="pt-8">
|
|
<div className="mb-6">
|
|
<div className="inline-flex items-center justify-center h-16 w-16 rounded-full bg-primary/10 text-primary">
|
|
<ShoppingCart className="h-8 w-8" />
|
|
</div>
|
|
</div>
|
|
<h3 className="text-xl font-semibold mb-3">
|
|
Place Your Order
|
|
</h3>
|
|
<p className="text-muted-foreground text-sm leading-relaxed">
|
|
Once confirmed, we'll process your order and arrange fast
|
|
shipping or local pickup.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className={`${insetCardClass} h-full hover:border-primary/30`}>
|
|
<CardContent className="pt-8">
|
|
<div className="mb-6">
|
|
<div className="inline-flex items-center justify-center h-16 w-16 rounded-full bg-primary/10 text-primary">
|
|
<Wrench className="h-8 w-8" />
|
|
</div>
|
|
</div>
|
|
<h3 className="text-xl font-semibold mb-3">
|
|
Get Expert Installation Support
|
|
</h3>
|
|
<p className="text-muted-foreground text-sm leading-relaxed">
|
|
Need help installing? Our team provides guidance or on-site
|
|
installation services.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Why Choose Us / Benefits Section */}
|
|
<section className="py-12 md:py-16 bg-muted/30">
|
|
<div className="container mx-auto px-4 max-w-6xl">
|
|
<div className="text-center mb-8 md:mb-12">
|
|
<h2 className="text-3xl font-bold tracking-tight md:text-4xl lg:text-5xl mb-4 text-balance">
|
|
Why Choose Rocky Mountain Vending for Parts?
|
|
</h2>
|
|
<p className="text-lg text-muted-foreground max-w-2xl mx-auto text-pretty leading-relaxed">
|
|
Trusted by businesses across Utah for reliable parts and
|
|
exceptional service
|
|
</p>
|
|
</div>
|
|
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
<Card className={`${insetCardClass} h-full hover:border-primary/30`}>
|
|
<CardContent className="pt-6">
|
|
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
|
|
<MapPin className="h-6 w-6 text-primary" />
|
|
</div>
|
|
<h3 className="text-lg font-semibold mb-2">
|
|
Local Expertise
|
|
</h3>
|
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
Serving Salt Lake City, Davis County, and Utah County for
|
|
over 10 years.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className={`${insetCardClass} h-full hover:border-primary/30`}>
|
|
<CardContent className="pt-6">
|
|
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
|
|
<Truck className="h-6 w-6 text-primary" />
|
|
</div>
|
|
<h3 className="text-lg font-semibold mb-2">Fast Delivery</h3>
|
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
Quick shipping or local pickup to minimize downtime for your
|
|
vending machines.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className={`${insetCardClass} h-full hover:border-primary/30`}>
|
|
<CardContent className="pt-6">
|
|
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
|
|
<Award className="h-6 w-6 text-primary" />
|
|
</div>
|
|
<h3 className="text-lg font-semibold mb-2">
|
|
Quality Assurance
|
|
</h3>
|
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
We source parts from trusted manufacturers to ensure
|
|
reliability and longevity.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className={`${insetCardClass} h-full hover:border-primary/30`}>
|
|
<CardContent className="pt-6">
|
|
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
|
|
<Users className="h-6 w-6 text-primary" />
|
|
</div>
|
|
<h3 className="text-lg font-semibold mb-2">Expert Support</h3>
|
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
Our technicians can guide you on selecting the right parts
|
|
or assist with installation.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className={`${insetCardClass} h-full hover:border-primary/30`}>
|
|
<CardContent className="pt-6">
|
|
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
|
|
<DollarSign className="h-6 w-6 text-primary" />
|
|
</div>
|
|
<h3 className="text-lg font-semibold mb-2">
|
|
Competitive Pricing
|
|
</h3>
|
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
Affordable parts tailored to your budget, with no compromise
|
|
on quality.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className={`${insetCardClass} h-full hover:border-primary/30`}>
|
|
<CardContent className="pt-6">
|
|
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
|
|
<Search className="h-6 w-6 text-primary" />
|
|
</div>
|
|
<h3 className="text-lg font-semibold mb-2">
|
|
Custom Solutions
|
|
</h3>
|
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
Need a hard-to-find part? We'll work with you to source or
|
|
recommend alternatives.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* FAQ Section */}
|
|
{faqs.length > 0 && <FAQSection faqs={faqs} />}
|
|
|
|
{/* Call to Action */}
|
|
<section className="py-12 md:py-16 bg-background">
|
|
<div className="container mx-auto px-4 max-w-4xl">
|
|
<Card className={`${surfaceCardClass} border-primary/20`}>
|
|
<CardContent className="p-6 md:p-8 text-center">
|
|
<div className="mb-6 inline-flex h-16 w-16 items-center justify-center rounded-full bg-primary/10">
|
|
<Phone className="h-8 w-8 text-primary" />
|
|
</div>
|
|
<h2 className="text-3xl md:text-4xl font-bold mb-4 tracking-tight text-balance">
|
|
Need Parts or Technical Support?
|
|
</h2>
|
|
<p className="text-lg text-muted-foreground mb-8 text-pretty leading-relaxed max-w-2xl mx-auto">
|
|
Our inventory is stocked with everything you need. Contact us
|
|
today for expert assistance and fast delivery.
|
|
</p>
|
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
|
<Link href="/contact-us">
|
|
<Button size="lg" className="group">
|
|
Get Help Now
|
|
<Phone className="ml-2 h-4 w-4 group-hover:translate-x-1 transition-transform" />
|
|
</Button>
|
|
</Link>
|
|
<Link href="/services">
|
|
<Button size="lg" variant="outline" className="group">
|
|
View All Services
|
|
<ArrowRight className="ml-2 h-4 w-4 group-hover:translate-x-1 transition-transform" />
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</section>
|
|
</>
|
|
)
|
|
} catch (error) {
|
|
if (process.env.NODE_ENV === "development") {
|
|
console.error("Error rendering Parts page:", error)
|
|
}
|
|
notFound()
|
|
}
|
|
}
|