Next.js website for Rocky Mountain Vending company featuring: - Product catalog with Stripe integration - Service areas and parts pages - Admin dashboard with Clerk authentication - SEO optimized pages with JSON-LD structured data Co-authored-by: Cursor <cursoragent@cursor.com>
792 lines
43 KiB
Text
792 lines
43 KiB
Text
import { notFound } from 'next/navigation';
|
|
import Link from 'next/link';
|
|
import { loadImageMapping, cleanHtmlEntities } from '@/lib/wordpress-content';
|
|
import { generateSEOMetadata, generateStructuredData } 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, Clock, Shield, ShoppingCart, ArrowRight, MapPin, Truck, Award, Users, DollarSign, Search, CreditCard } from 'lucide-react';
|
|
import { FAQSection } from '@/components/faq-section';
|
|
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 generateSEOMetadata({
|
|
title: page.title || 'Vending Machine Parts & Support',
|
|
description: page.seoDescription || page.excerpt || '',
|
|
excerpt: page.excerpt,
|
|
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;
|
|
|
|
let structuredData;
|
|
try {
|
|
structuredData = generateStructuredData({
|
|
title: page.title || 'Vending Machine Parts & Support',
|
|
description: page.seoDescription || page.excerpt || '',
|
|
url: page.link || page.urlPath || `https://rockymountainvending.com/services/parts/`,
|
|
datePublished: page.date,
|
|
dateModified: page.modified || page.date,
|
|
type: 'WebPage',
|
|
});
|
|
} catch (e) {
|
|
structuredData = {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'WebPage',
|
|
headline: page.title || 'Vending Machine Parts & Support',
|
|
description: page.seoDescription || '',
|
|
url: `https://rockymountainvending.com/services/parts/`,
|
|
};
|
|
}
|
|
|
|
const cleanExcerpt = page.excerpt ? cleanHtmlEntities(page.excerpt)
|
|
.replace(/…/g, '...')
|
|
.replace(/\[…\]/g, '...')
|
|
.replace(/\[\.\.\.\]/g, '...')
|
|
.replace(/\[\.\.\./g, '...')
|
|
.replace(/\.\.\.\]/g, '...')
|
|
.replace(/\[\.\.\./g, '...')
|
|
.replace(/\[\.\./g, '...')
|
|
.replace(/\.\.\]/g, '...')
|
|
.trim() : '';
|
|
|
|
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">
|
|
<header className="text-center mb-6">
|
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight text-balance mb-4">
|
|
{page.title || 'Vending Machine Parts & Support'}
|
|
</h1>
|
|
{cleanExcerpt && (
|
|
<p className="text-lg md:text-xl text-muted-foreground max-w-3xl mx-auto text-pretty leading-relaxed">
|
|
{cleanExcerpt}
|
|
</p>
|
|
)}
|
|
</header>
|
|
</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">
|
|
<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>
|
|
</article>
|
|
</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">
|
|
<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">Parts & Support Services</h2>
|
|
<p className="text-lg text-muted-foreground max-w-2xl mx-auto text-pretty leading-relaxed">
|
|
Comprehensive parts and technical support to keep your vending machines operational
|
|
</p>
|
|
</div>
|
|
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
<Card className="border-border/50 hover:border-secondary/50 transition-colors shadow-sm">
|
|
<CardContent className="pt-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="border-border/50 hover:border-secondary/50 transition-colors shadow-sm">
|
|
<CardContent className="pt-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="border-border/50 hover:border-secondary/50 transition-colors shadow-sm">
|
|
<CardContent className="pt-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="border-border/50 hover:border-secondary/50 transition-colors shadow-sm">
|
|
<CardContent className="pt-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="border-border/50 hover:border-secondary/50 transition-colors shadow-sm">
|
|
<CardContent className="pt-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="border-border/50 hover:border-secondary/50 transition-colors shadow-sm">
|
|
<CardContent className="pt-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">
|
|
<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">Available Parts Include</h2>
|
|
<p className="text-lg text-muted-foreground max-w-2xl mx-auto text-pretty leading-relaxed">
|
|
We stock a comprehensive inventory of vending machine parts to keep your equipment running smoothly
|
|
</p>
|
|
</div>
|
|
<Card className="border-border/50 shadow-lg">
|
|
<CardHeader>
|
|
<CardTitle className="text-2xl md:text-3xl">Parts Inventory</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="pt-6">
|
|
<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="border border-border/50 rounded-lg px-6 py-2 bg-card shadow-sm hover:shadow-md hover:border-secondary/50 transition-all">
|
|
<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="border border-border/50 rounded-lg px-6 py-2 bg-card shadow-sm hover:shadow-md hover:border-secondary/50 transition-all">
|
|
<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="border border-border/50 rounded-lg px-6 py-2 bg-card shadow-sm hover:shadow-md hover:border-secondary/50 transition-all">
|
|
<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="border-border/50 hover:border-secondary/50 transition-colors shadow-sm">
|
|
<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="border-border/50 hover:border-secondary/50 transition-colors shadow-sm">
|
|
<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="border-border/50 hover:border-secondary/50 transition-colors shadow-sm">
|
|
<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="border-border/50 hover:border-secondary/50 transition-colors shadow-sm">
|
|
<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="border-border/50 hover:border-secondary/50 transition-colors shadow-sm">
|
|
<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="border-border/50 hover:border-secondary/50 transition-colors shadow-sm">
|
|
<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="border-border/50 hover:border-secondary/50 transition-colors shadow-sm">
|
|
<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="border-border/50 hover:border-secondary/50 transition-colors shadow-sm">
|
|
<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="border-border/50 hover:border-secondary/50 transition-colors shadow-sm">
|
|
<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="border-border/50 hover:border-secondary/50 transition-colors shadow-sm">
|
|
<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="border-2 border-primary/20 shadow-lg">
|
|
<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();
|
|
}
|
|
}
|