From 0be731e474af256732a5995b6df0bf3cd901c713 Mon Sep 17 00:00:00 2001 From: DMleadgen Date: Sat, 4 Apr 2026 07:46:46 -0600 Subject: [PATCH] deploy: unify public UI and mobile chat --- app/globals.css | 153 +++-- app/manuals/page.tsx | 130 ++-- app/products/[id]/page.tsx | 109 ++-- app/service-areas/page.tsx | 10 +- app/services/moving/page.tsx | 160 +++-- app/services/repairs/page.tsx | 71 +-- app/services/service-areas/page.tsx | 332 +++++------ components/about-page.tsx | 100 ++-- components/contact-page.tsx | 160 +++-- components/footer.tsx | 50 +- components/header.tsx | 612 ++++++++++--------- components/hero-section.tsx | 199 ++++--- components/how-it-works-section.tsx | 71 +-- components/location-landing-page.tsx | 55 +- components/product-showcase-section.tsx | 15 +- components/public-surface.tsx | 80 ++- components/repairs-image-carousel.tsx | 22 +- components/reviews-page.tsx | 2 +- components/service-areas-section.tsx | 118 ++-- components/site-chat-widget.tsx | 757 +++++++++++++++--------- components/stats-section.tsx | 37 +- components/vending-machines-page.tsx | 2 +- 22 files changed, 1837 insertions(+), 1408 deletions(-) diff --git a/app/globals.css b/app/globals.css index 739203be..9bacf058 100644 --- a/app/globals.css +++ b/app/globals.css @@ -10,20 +10,20 @@ --card-foreground: oklch(0.178 0.014 275.627); --popover: oklch(1 0 0); --popover-foreground: oklch(0.178 0.014 275.627); - --primary: #29A047; /* Primary brand color (green from logo) */ + --primary: #29a047; /* Primary brand color (green from logo) */ --primary-foreground: oklch(0.989 0.003 106.423); --primary-dark: #1d7a35; /* Darker primary for gradients and hover states */ - --secondary: #54595F; /* Secondary color (gray) */ + --secondary: #54595f; /* Secondary color (gray) */ --secondary-foreground: oklch(1 0 0); --muted: oklch(0.961 0.004 106.423); --muted-foreground: oklch(0.556 0.014 275.627); - --accent: #C4142C; /* Accent color (red - matches link hover) */ + --accent: #c4142c; /* Accent color (red - matches link hover) */ --accent-foreground: oklch(1 0 0); --destructive: oklch(0.577 0.245 27.325); --destructive-foreground: oklch(0.985 0 0); --border: oklch(0.922 0.004 106.423); --input: oklch(0.922 0.004 106.423); - --ring: #29A047; /* Primary color for focus rings */ + --ring: #29a047; /* Primary color for focus rings */ --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); @@ -38,26 +38,33 @@ --sidebar-accent-foreground: oklch(0.205 0 0); --sidebar-border: oklch(0.922 0 0); --sidebar-ring: oklch(0.708 0 0); - + /* Link Colors - Master Style Guide */ --link-color: var(--foreground); --link-hover-color: #c4142c; /* Red: rgb(196, 20, 44) */ --link-hover-color-dark: #a01020; /* Darker red for gradients and hover states */ --link-hover-bg: rgba(196, 20, 44, 0.1); - --header-bg: #ffffff; - --footer-bg: #fef3e0; - --shadow: 0 2px 4px rgba(0,0,0,0.05); + --header-bg: #ffffff; + --footer-bg: #fef3e0; + --shadow: 0 2px 4px rgba(0, 0, 0, 0.05); - /* Custom brand colors */ - --yellow: #FCBA09; - --orange: #F79611; - --mountain-bubbles: #FCBA0924; /* Yellow with transparency */ + /* Custom brand colors */ + --yellow: #fcba09; + --orange: #f79611; + --mountain-bubbles: #fcba0924; /* Yellow with transparency */ + --public-shell-max: 80rem; + --public-section-space: clamp(4.5rem, 7vw, 7rem); + --public-surface-radius: 2rem; + --public-inset-radius: 1.5rem; + --public-surface-shadow: 0 22px 56px rgba(15, 23, 42, 0.08); + --public-surface-shadow-hover: 0 28px 72px rgba(15, 23, 42, 0.12); + --header-height: 5.25rem; - /* Increased spacing variables */ - --spacing-xs: 0.75rem; - --spacing-sm: 1.25rem; - } + /* Increased spacing variables */ + --spacing-xs: 0.75rem; + --spacing-sm: 1.25rem; +} .dark { --background: oklch(0.145 0.01 275.627); @@ -92,7 +99,7 @@ --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(0.269 0 0); --sidebar-ring: oklch(0.439 0 0); - + /* Link Colors - Dark Mode */ --link-color: var(--foreground); --link-hover-color: #ff4d6d; /* Lighter red for dark mode visibility */ @@ -101,8 +108,11 @@ } @theme inline { - --font-sans: var(--font-inter), "Inter", "Inter Fallback", system-ui, -apple-system, sans-serif; - --font-mono: var(--font-geist-mono), "Geist Mono", "Geist Mono Fallback", monospace; + --font-sans: + var(--font-inter), "Inter", "Inter Fallback", system-ui, -apple-system, + sans-serif; + --font-mono: + var(--font-geist-mono), "Geist Mono", "Geist Mono Fallback", monospace; --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); @@ -147,44 +157,70 @@ } body { @apply bg-background text-foreground; - font-family: var(--font-inter), "Inter", system-ui, -apple-system, sans-serif; + font-family: + var(--font-inter), + "Inter", + system-ui, + -apple-system, + sans-serif; + line-height: 1.5; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } html { scroll-behavior: smooth; /* Added smooth scroll behavior for Apple-like experience */ + scroll-padding-top: calc(var(--header-height) + 1.25rem); } - + /* Global Link Styling - Master Style Guide */ /* All hyperlinks should highlight in red on hover */ a { color: var(--link-color); text-decoration: none; - transition: color 0.2s ease, background-color 0.2s ease; + text-decoration-color: transparent; + text-decoration-thickness: 0.08em; + text-underline-offset: 0.18em; + transition: + color 0.2s ease, + text-decoration-color 0.2s ease, + opacity 0.2s ease; } - + a:hover, a:focus { color: var(--link-hover-color); - background-color: var(--link-hover-bg); + background-color: transparent; } - + a:active { color: var(--link-hover-color); } - + /* Next.js Link components inherit the same styling */ a[href] { color: var(--link-color); - transition: color 0.2s ease, background-color 0.2s ease; + transition: + color 0.2s ease, + text-decoration-color 0.2s ease, + opacity 0.2s ease; } - + a[href]:hover, a[href]:focus { color: var(--link-hover-color); - background-color: var(--link-hover-bg); + background-color: transparent; } - + + a:focus-visible, + button:focus-visible, + [role="button"]:focus-visible, + input:focus-visible, + select:focus-visible, + textarea:focus-visible { + outline: none; + box-shadow: 0 0 0 4px color-mix(in srgb, var(--primary) 18%, transparent); + } + /* Hide Next.js dev tools portal */ nextjs-portal, nextjs-portal * { @@ -193,7 +229,7 @@ opacity: 0 !important; pointer-events: none !important; } - + /* WordPress content styling */ .prose strong, article strong, @@ -201,13 +237,13 @@ font-weight: 600; color: var(--foreground); } - + .prose em, article em, .wordpress-content em { font-style: italic; } - + .prose ul, .prose ol, article ul, @@ -218,7 +254,7 @@ margin-bottom: 1rem; padding-left: 1.5rem; } - + .prose li, article li, .wordpress-content li { @@ -239,6 +275,26 @@ color: var(--link-hover-color); } + .public-page { + margin-inline: auto; + width: 100%; + max-width: var(--public-shell-max); + padding-inline: 1rem; + padding-block: 2.5rem 3.75rem; + } + + @media (min-width: 640px) { + .public-page { + padding-inline: 1.25rem; + } + } + + @media (min-width: 768px) { + .public-page { + padding-block: 3rem 4.5rem; + } + } + .wordpress-content h1, .wordpress-content h2, .wordpress-content h3, @@ -271,7 +327,7 @@ width: 100%; height: auto; } - + .wordpress-content > div img, .wordpress-content figure img { @apply mx-auto block; @@ -285,44 +341,49 @@ .wordpress-content > div { @apply space-y-6; } - + /* Visual Separation for Page Sections */ section { @apply relative; } - + /* Main content wrapper for visual separation from header */ main { @apply relative; box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.05); background: var(--background); } - + /* Improved spacing and readability */ p { line-height: 1.7; } - - h1, h2, h3, h4, h5, h6 { + + h1, + h2, + h3, + h4, + h5, + h6 { letter-spacing: -0.02em; line-height: 1.2; color: var(--foreground); } - + /* Footer Styling - Only apply to top-level footer, not article footers */ body > div > footer { background: var(--footer-bg); box-shadow: var(--shadow); } - + /* Hide scrollbar for horizontal scrolling galleries */ .scrollbar-hide { - -ms-overflow-style: none; /* IE and Edge */ - scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ } - + .scrollbar-hide::-webkit-scrollbar { - display: none; /* Chrome, Safari and Opera */ + display: none; /* Chrome, Safari and Opera */ } /* Focus visible styling for keyboard navigation - Following Vercel Web Design Guidelines */ diff --git a/app/manuals/page.tsx b/app/manuals/page.tsx index 05cee685..dfbd5b79 100644 --- a/app/manuals/page.tsx +++ b/app/manuals/page.tsx @@ -1,80 +1,44 @@ export const dynamic = "force-dynamic" -import { existsSync } from 'fs' -import { join } from 'path' -import { Metadata } from 'next' -import { businessConfig } from '@/lib/seo-config' -import { ManualsPageExperience } from '@/components/manuals-page-experience' -import { listConvexManuals } from '@/lib/convex-service' -import { scanManuals } from '@/lib/manuals' -import { selectManualsForSite } from '@/lib/manuals-site-selection' -import { generateStructuredData } from '@/lib/seo' -import { getManualsThumbnailsRoot } from '@/lib/manuals-paths' +import { existsSync } from "fs" +import { join } from "path" +import { Metadata } from "next" +import { businessConfig } from "@/lib/seo-config" +import { ManualsPageExperience } from "@/components/manuals-page-experience" +import { listConvexManuals } from "@/lib/convex-service" +import { scanManuals } from "@/lib/manuals" +import { selectManualsForSite } from "@/lib/manuals-site-selection" +import { generateSEOMetadata, generateStructuredData } from "@/lib/seo" +import { getManualsThumbnailsRoot } from "@/lib/manuals-paths" -export const metadata: Metadata = { - title: 'Vending Machine Manuals | Download PDF Guides | Rocky Mountain Vending', +export const metadata: Metadata = generateSEOMetadata({ + title: "Vending Machine Manuals | Rocky Mountain Vending", description: - 'Download free PDF manuals, service guides, and parts documentation for hundreds of vending machine models from Royal Vendors, Dixie-Narco, Vendo, Crane, BevMax, Merchant Series, AP, GPL, Seaga, USI, and more. Find service manuals, parts catalogs, installation instructions, troubleshooting guides, and maintenance documentation for snack, beverage, combo, coffee, and food vending machines. Many manuals include available replacement parts with purchase links.', + "Browse vending machine manuals, service guides, and support documentation for snack, beverage, combo, coffee, and food machines.", + path: "/manuals", keywords: [ - 'vending machine manuals', - 'vending machine PDF', - 'vending machine service manual', - 'vending machine parts catalog', - 'vending machine repair manual', - 'vending machine installation guide', - 'vending machine troubleshooting guide', - 'Royal Vendors manual', - 'Dixie-Narco manual', - 'Vendo manual', - 'Crane vending manual', - 'BevMax manual', - 'Merchant Series manual', - 'AP vending manual', - 'GPL vending manual', - 'Seaga vending manual', - 'USI vending manual', - 'snack machine manual', - 'beverage machine manual', - 'combo vending machine manual', - 'coffee vending machine manual', - 'food vending machine manual', - 'frozen food vending manual', - 'vending machine parts', - 'vending machine replacement parts', - 'vending machine wiring diagram', - 'vending machine maintenance', + "vending machine manuals", + "vending machine PDF", + "vending machine service manual", + "vending machine repair manual", + "vending machine troubleshooting guide", + "Royal Vendors manual", + "Dixie-Narco manual", + "Vendo manual", + "Crane vending manual", + "Seaga vending manual", ], - openGraph: { - title: 'Vending Machine Manuals | Download PDF Guides | Rocky Mountain Vending', - description: - 'Download free PDF manuals, service guides, and parts documentation for hundreds of vending machine models from leading manufacturers. Find service manuals, parts catalogs, installation instructions, troubleshooting guides, and maintenance documentation for snack, beverage, combo, coffee, and food vending machines.', - type: 'website', - url: `${businessConfig.website}/manuals`, - images: [ - { - url: `${businessConfig.website}/images/rocky-mountain-vending-service-area-926x1024.webp`, - width: 926, - height: 1024, - alt: 'Rocky Mountain Vending Manuals', - }, - ], - }, - twitter: { - card: 'summary_large_image', - title: 'Vending Machine Manuals | Download PDF Guides', - description: - 'Download free PDF manuals, service guides, and parts documentation for hundreds of vending machine models from Royal Vendors, Dixie-Narco, Vendo, Crane, BevMax, and more. Find service manuals, parts catalogs, installation instructions, and troubleshooting guides.', - }, - alternates: { - canonical: `${businessConfig.website}/manuals`, - }, -} +}) export default async function ManualsPage() { // Prefer Convex-backed metadata, but keep filesystem fallback in place until the shared catalog is fully populated. const convexManuals = await listConvexManuals() - const allManuals = convexManuals.length > 0 ? convexManuals : await scanManuals() - let manuals = convexManuals.length > 0 ? convexManuals : selectManualsForSite(allManuals).manuals + const allManuals = + convexManuals.length > 0 ? convexManuals : await scanManuals() + let manuals = + convexManuals.length > 0 + ? convexManuals + : selectManualsForSite(allManuals).manuals // Hide broken local thumbnails so the public manuals page doesn't spam 404s. const thumbnailsRoot = getManualsThumbnailsRoot() @@ -83,43 +47,43 @@ export default async function ManualsPage() { return manual } - const relativeThumbnailPath = manual.thumbnailUrl.includes('/thumbnails/') - ? manual.thumbnailUrl.replace(/^.*\/thumbnails\//, '') + const relativeThumbnailPath = manual.thumbnailUrl.includes("/thumbnails/") + ? manual.thumbnailUrl.replace(/^.*\/thumbnails\//, "") : manual.thumbnailUrl return existsSync(join(thumbnailsRoot, relativeThumbnailPath)) ? manual : { ...manual, thumbnailUrl: undefined } }) - + // Generate structured data for SEO const structuredData = generateStructuredData({ - title: 'Vending Machine Manuals', + title: "Vending Machine Manuals", description: - 'Download free PDF manuals, service guides, and parts documentation for hundreds of vending machine models from Royal Vendors, Dixie-Narco, Vendo, Crane, BevMax, Merchant Series, AP, GPL, Seaga, USI, and more. Find service manuals, parts catalogs, installation instructions, troubleshooting guides, and maintenance documentation for snack, beverage, combo, coffee, and food vending machines. Many manuals include available replacement parts with purchase links.', + "Download free PDF manuals, service guides, and parts documentation for hundreds of vending machine models from Royal Vendors, Dixie-Narco, Vendo, Crane, BevMax, Merchant Series, AP, GPL, Seaga, USI, and more. Find service manuals, parts catalogs, installation instructions, troubleshooting guides, and maintenance documentation for snack, beverage, combo, coffee, and food vending machines. Many manuals include available replacement parts with purchase links.", url: `${businessConfig.website}/manuals`, - type: 'WebPage', + type: "WebPage", }) // Add CollectionPage schema for better SEO const collectionSchema = { - '@context': 'https://schema.org', - '@type': 'CollectionPage', - name: 'Vending Machine Manuals', + "@context": "https://schema.org", + "@type": "CollectionPage", + name: "Vending Machine Manuals", description: - 'A comprehensive collection of vending machine manuals, service guides, and parts documentation from leading manufacturers including Royal Vendors, Dixie-Narco, Vendo, Crane Merchandising, BevMax, Merchant Series, AP, GPL, Seaga, USI, and more. Includes service manuals, parts catalogs, installation instructions, troubleshooting guides, wiring diagrams, and maintenance documentation for snack machines, beverage machines, combo vending machines, coffee machines, food machines, and frozen food machines. Many manuals feature available replacement parts with direct purchase links.', + "A comprehensive collection of vending machine manuals, service guides, and parts documentation from leading manufacturers including Royal Vendors, Dixie-Narco, Vendo, Crane Merchandising, BevMax, Merchant Series, AP, GPL, Seaga, USI, and more. Includes service manuals, parts catalogs, installation instructions, troubleshooting guides, wiring diagrams, and maintenance documentation for snack machines, beverage machines, combo vending machines, coffee machines, food machines, and frozen food machines. Many manuals feature available replacement parts with direct purchase links.", url: `${businessConfig.website}/manuals`, mainEntity: { - '@type': 'ItemList', + "@type": "ItemList", numberOfItems: manuals.length, itemListElement: manuals.slice(0, 50).map((manual, index) => ({ - '@type': 'ListItem', + "@type": "ListItem", position: index + 1, item: { - '@type': 'DigitalDocument', - name: manual.filename.replace(/\.pdf$/i, ''), + "@type": "DigitalDocument", + name: manual.filename.replace(/\.pdf$/i, ""), description: `${manual.manufacturer} ${manual.category} Manual`, - encodingFormat: 'application/pdf', + encodingFormat: "application/pdf", }, })), }, @@ -135,7 +99,7 @@ export default async function ManualsPage() { type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(collectionSchema) }} /> -
+
diff --git a/app/products/[id]/page.tsx b/app/products/[id]/page.tsx index 2bbe82e9..4a53f94b 100644 --- a/app/products/[id]/page.tsx +++ b/app/products/[id]/page.tsx @@ -1,35 +1,37 @@ -import { notFound } from 'next/navigation' -import Image from 'next/image' -import { fetchProductById, fetchAllProducts } from '@/lib/stripe/products' -import { AddToCartButton } from '@/components/add-to-cart-button' -import { Card, CardContent } from '@/components/ui/card' +import { notFound } from "next/navigation" +import Image from "next/image" +import { fetchProductById, fetchAllProducts } from "@/lib/stripe/products" +import { AddToCartButton } from "@/components/add-to-cart-button" +import { PublicInset, PublicSurface } from "@/components/public-surface" interface ProductPageProps { params: Promise<{ id: string }> } // Required for static export -export const dynamic = 'force-static'; -export const dynamicParams = false; +export const dynamic = "force-static" +export const dynamicParams = false // Generate static params for all products export async function generateStaticParams() { try { - const products = await fetchAllProducts(); - + const products = await fetchAllProducts() + // Ensure we have products if (!products || products.length === 0) { - console.warn('No products found for static generation. Product pages will not be pre-rendered.'); - return []; + console.warn( + "No products found for static generation. Product pages will not be pre-rendered." + ) + return [] } - + return products.map((product) => ({ id: product.id, - })); + })) } catch (error) { - console.error('Error generating static params for products:', error); + console.error("Error generating static params for products:", error) // Return empty array - product pages won't be pre-rendered but won't break the build - return []; + return [] } } @@ -39,7 +41,7 @@ export async function generateMetadata({ params }: ProductPageProps) { if (!product) { return { - title: 'Product Not Found | Rocky Mountain Vending', + title: "Product Not Found | Rocky Mountain Vending", } } @@ -57,69 +59,70 @@ export default async function ProductPage({ params }: ProductPageProps) { notFound() } - const imageUrl = product.images?.[0] || '/placeholder.svg' + const imageUrl = product.images?.[0] || "/placeholder.svg" return ( -
-
- {/* Product Image */} - - -
- {product.name} -
-
-
- - {/* Product Details */} -
-

{product.name}

+
+
+ +
+ {product.name} +
+
+
-

+

+ {product.name} +

+

${product.price.toFixed(2)} {product.currency.toUpperCase()}

{product.description && ( -
-

Description

+ +

Description

'), + __html: product.description.replace(/\n/g, "
"), }} /> -
+
)} {product.metadata && Object.keys(product.metadata).length > 0 && ( -
-

Specifications

-
+ +

Specifications

+
{Object.entries(product.metadata).map(([key, value]) => ( -
-
{key}:
+
+
+ {key} +
{value}
))}
-
+ )} -
+
-
+
) } - diff --git a/app/service-areas/page.tsx b/app/service-areas/page.tsx index dc9f0196..fdff7e1a 100644 --- a/app/service-areas/page.tsx +++ b/app/service-areas/page.tsx @@ -100,7 +100,7 @@ export default function ServiceAreasPage() { ) return ( -
+

- If you're close to one of our current routes, we may still - be able to help. Reach out and we'll confirm whether your + If you're close to one of our current routes, we may still be + able to help. Reach out and we'll confirm whether your location fits our current coverage and service schedule.

@@ -169,8 +169,8 @@ export default function ServiceAreasPage() {

From free placement and machine sales to repairs, moving, and - parts help, we help Utah businesses keep vending available - without having to manage the machines themselves. + parts help, we help Utah businesses keep vending available without + having to manage the machines themselves.

diff --git a/app/services/moving/page.tsx b/app/services/moving/page.tsx index 1e9053cc..caf9b4b2 100644 --- a/app/services/moving/page.tsx +++ b/app/services/moving/page.tsx @@ -23,7 +23,7 @@ export const metadata: Metadata = generateSEOMetadata({ export default function MovingServicesPage() { return ( -
+
- - -
-

- At Rocky Mountain Vending LLC, we specialize in the safe and - efficient relocation of vending machines of all types and sizes - — from compact snack machines to full-size refrigerated beverage - and combo units. Whether you're rearranging equipment within a - building, moving to a new location, or removing an old machine, - our experienced team handles every detail to minimize downtime - and protect your investment. -

-

- Vending machines are heavy (often 400–900+ lbs), delicate, and - require specialized handling to avoid damage to internal - components like compressors, electronics, glass fronts, or - refrigeration systems. Attempting a DIY move can lead to costly - repairs, injuries, or property damage. We use proven techniques - and professional-grade equipment to ensure a smooth, damage-free - process every time. -

-
-
-
+ +
+

+ At Rocky Mountain Vending LLC, we specialize in the safe and + efficient relocation of vending machines of all types and sizes — + from compact snack machines to full-size refrigerated beverage and + combo units. Whether you're rearranging equipment within a + building, moving to a new location, or removing an old machine, + our experienced team handles every detail to minimize downtime and + protect your investment. +

+

+ Vending machines are heavy (often 400–900+ lbs), delicate, and + require specialized handling to avoid damage to internal + components like compressors, electronics, glass fronts, or + refrigeration systems. Attempting a DIY move can lead to costly + repairs, injuries, or property damage. We use proven techniques + and professional-grade equipment to ensure a smooth, damage-free + process every time. +

+
+
{/* Image Gallery Section */} @@ -75,10 +73,7 @@ export default function MovingServicesPage() { {/* Image 1 */} -
+
Vending machine securely packaged for transport with dark green protective blankets, clear shrink wrap, bright yellow straps, and yellow corner protectors on wooden pallet - professional moving service in Utah -
+
Vending machine wrapped in dark blue protective blankets and clear shrink wrap with yellow straps, foam padding, and safety markings secured on wooden pallets for safe transport -
+
Utah vending machine moving professionals transporting commercial vending equipment with secure transport methods and fully insured relocation services Why Choose Us for Your Vending Move? - - -
    -
  • - - - - Years of hands-on vending industry experience - {" "} - — we understand the unique vulnerabilities of snack, drink, - combo, and refrigerated machines. - -
  • -
  • - - - Fully insured for - complete peace of mind. - -
  • -
  • - - - - Minimal disruption: - {" "} - Fast, coordinated service scheduled around your business - hours. - -
  • -
  • - - - - One-machine or multi-machine jobs - {" "} - handled efficiently. - -
  • -
  • - - - - Commitment to safety - {" "} - for our team, your staff, and your equipment. - -
  • -
-
-
+ +
    +
  • + + + + Years of hands-on vending industry experience + {" "} + — we understand the unique vulnerabilities of snack, drink, + combo, and refrigerated machines. + +
  • +
  • + + + Fully insured for + complete peace of mind. + +
  • +
  • + + + Minimal disruption:{" "} + Fast, coordinated service scheduled around your business hours. + +
  • +
  • + + + + One-machine or multi-machine jobs + {" "} + handled efficiently. + +
  • +
  • + + + + Commitment to safety + {" "} + for our team, your staff, and your equipment. + +
  • +
+
{/* CTA Section */} diff --git a/app/services/repairs/page.tsx b/app/services/repairs/page.tsx index 7834124c..ebf232ed 100644 --- a/app/services/repairs/page.tsx +++ b/app/services/repairs/page.tsx @@ -1,6 +1,9 @@ import { notFound } from "next/navigation" import { loadImageMapping } from "@/lib/wordpress-content" -import { generateSEOMetadata, generateStructuredData } from "@/lib/seo" +import { + generateRegistryMetadata, + generateRegistryStructuredData, +} from "@/lib/seo" import { getPageBySlug } from "@/lib/wordpress-data-loader" import { cleanWordPressContent } from "@/lib/clean-wordPress-content" import { ServicesSection } from "@/components/services-section" @@ -18,7 +21,11 @@ import { import { RepairsImageCarousel } from "@/components/repairs-image-carousel" import { ContactForm } from "@/components/forms/contact-form" import { Breadcrumbs } from "@/components/breadcrumbs" -import { PublicInset, PublicSurface } from "@/components/public-surface" +import { + PublicInset, + PublicPageHeader, + PublicSurface, +} from "@/components/public-surface" import Image from "next/image" import Link from "next/link" import { ArrowRight } from "lucide-react" @@ -38,14 +45,10 @@ export async function generateMetadata(): Promise { } } - return generateSEOMetadata({ - title: page.title || "Vending Machine Repairs", - description: page.seoDescription || page.excerpt || "", - excerpt: page.excerpt, + return generateRegistryMetadata("repairs", { date: page.date, modified: page.modified, image: page.images?.[0]?.localPath, - path: "/services/repairs", }) } @@ -140,28 +143,10 @@ export default async function RepairsPage() {

No content available.

) - let structuredData - try { - structuredData = generateStructuredData({ - title: page.title || "Vending Machine Repairs", - description: page.seoDescription || page.excerpt || "", - url: - page.link || - page.urlPath || - `https://rockymountainvending.com/services/repairs/`, - datePublished: page.date, - dateModified: page.modified || page.date, - type: "WebPage", - }) - } catch (e) { - structuredData = { - "@context": "https://schema.org", - "@type": "WebPage", - headline: page.title || "Vending Machine Repairs", - description: page.seoDescription || "", - url: `https://rockymountainvending.com/services/repairs/`, - } - } + const structuredData = generateRegistryStructuredData("repairs", { + datePublished: page.date, + dateModified: page.modified || page.date, + }) return ( <> @@ -179,11 +164,15 @@ export default async function RepairsPage() { { label: "Repairs", href: "/services/repairs" }, ]} /> -
-

- {page.title || "Vending Machine Repairs and Service"} -

-

+ +

Rocky Mountain Vending delivers expert{" "} {" "} services, contact us today for fast, professional solutions!

-
+ {/* Images Carousel */} -
+ -
+
@@ -576,10 +565,10 @@ export default async function RepairsPage() { If you don't see your brand listed, feel free to reach out! We may still be able to service it, but there are some brands we may not support.{" "} - + Contact us , and we'll let you know how we can help. diff --git a/app/services/service-areas/page.tsx b/app/services/service-areas/page.tsx index 0ebc13b0..33bf2ca2 100644 --- a/app/services/service-areas/page.tsx +++ b/app/services/service-areas/page.tsx @@ -2,9 +2,14 @@ import type { Metadata } from "next" import Link from "next/link" import { getAllLocations } from "@/lib/location-data" import { businessConfig } from "@/lib/seo-config" -import { Card, CardContent } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { ArrowRight, MapPin } from "lucide-react" +import { + PublicInset, + PublicPageHeader, + PublicSectionHeader, + PublicSurface, +} from "@/components/public-surface" export const metadata: Metadata = { title: "Utah Service Areas | Rocky Mountain Vending", @@ -92,209 +97,156 @@ export default function ServiceAreasServicesPage() { }, ] + const counties = [ + { + title: "Salt Lake County", + description: + "Serving 14 cities in Salt Lake County with reliable vending services.", + locations: saltLakeCounty, + }, + { + title: "Davis County", + description: + "Supporting businesses from Ogden to Layton with reliable vending services.", + locations: davisCounty, + }, + { + title: "Utah County", + description: + "Serving Provo and surrounding areas with quality vending services.", + locations: utahCounty, + }, + ] + return ( -
-
-

- Vending Machine Services by Location -

-

- Rocky Mountain Vending provides comprehensive vending machine services - across 20 service areas in Utah. Find services available in your city - below. -

-
+
+ - {/* Services Overview */} -
-

- Our Services -

-
- {services.map((service, index) => ( - - +
+ + +
+ {services.map((service, index) => ( +

{service.title}

-

{service.description}

- - - ))} -
+

+ {service.description} +

+ + ))} +
+
- {/* Salt Lake County */} -
-
-

- Salt Lake County -

-

- Serving 14 cities in Salt Lake County with reliable vending services -

-
-
- {saltLakeCounty.map((location) => ( - - -
-
+
+ {counties.map((county) => ( +
+
+ +
+ +
+ {county.locations.map((location) => ( + +

{location.city}

-

- +

+ {location.zipCode}

-
-
-

- Services Available: -

-
    -
  • • Vending Machine Sales
  • -
  • • Repair Services
  • -
  • • Healthy Options
  • -
  • • Maintenance
  • -
-
- - - - - - ))} -
-
- {/* Davis County */} -
-
-

- Davis County +
+

+ Services Available +

+
    +
  • Vending machine sales
  • +
  • Repair services
  • +
  • Healthy snack and beverage options
  • +
  • Maintenance and restocking
  • +
+
+ + + + + + ))} +

+
+ ))} +
+ +
+ +

+ Ready to get started?

-

- Supporting businesses from Ogden to Layton with reliable vending - services +

+ All services are available across our coverage area. Reach out and + we'll help you figure out the right next step for your + location.

-
-
- {davisCounty.map((location) => ( - - -
-
-

- {location.city} -

-

- - {location.zipCode} -

-
-
-
-

- Services Available: -

-
    -
  • • Vending Machine Sales
  • -
  • • Repair Services
  • -
  • • Healthy Options
  • -
  • • Maintenance
  • -
-
- - - -
-
- ))} -
- - - {/* Utah County */} -
-
-

- Utah County -

-

- Serving Provo and surrounding areas with quality vending services -

-
-
- {utahCounty.map((location) => ( - - -
-
-

- {location.city} -

-

- - {location.zipCode} -

-
-
-
-

- Services Available: -

-
    -
  • • Vending Machine Sales
  • -
  • • Repair Services
  • -
  • • Healthy Options
  • -
  • • Maintenance
  • -
-
- - - -
-
- ))} -
-
- - {/* Call to Action */} -
-

- Ready to Get Started? -

-

- All services are available across all 20 service areas. Contact us - today to learn more about vending machine solutions for your business. -

-
- - - - - - -
+
+ + + + + + +
+ +

+ This route stays available for legacy links, but the primary + service-area experience lives on the main{" "} + + Utah service areas page + + . +

+
+
) diff --git a/components/about-page.tsx b/components/about-page.tsx index bb7c8632..2cc0ab61 100644 --- a/components/about-page.tsx +++ b/components/about-page.tsx @@ -1,58 +1,72 @@ -'use client' +"use client" -import Image from 'next/image' -import { Card, CardContent } from "@/components/ui/card" +import Image from "next/image" +import { + PublicInset, + PublicPageHeader, + PublicSurface, +} from "@/components/public-surface" export function AboutPage() { return ( -
- {/* Header */} -
-

About Us

-
-
+
+ - {/* Main Content - Image and Text */} -
- {/* Left Column - Image */} -
- - + +
+
+ Matt and Rebekah - - -
+ +
- {/* Right Column - Text Content */} -
- - -
-

- When my wife, Rebekah, and I met we knew that we wanted to start a business and raise our family here in the Salt Lake Valley. We met in Bellevue Washington and as soon we got married moved straight to Salt Lake City to start Rocky Mountain Vending. Our focus is on Healthy Vending and providing the best service. Of course we can also provide traditional vending options as well. -

-

- Rocky Mountain Vending is 100% a local family run business founded in 2019. If you are looking for the old-fashioned business relationship with all the perks of the 21st century. -

-

- We strongly believe that business should be founded on trust, exceptional customer service, employing the latest technology, and of course having fun while we work. -

-

- ~Matt -

-
-
-
+
+
+

+ When my wife, Rebekah, and I met, we knew we wanted to start a + business and raise our family here in the Salt Lake Valley. We + met in Bellevue, Washington and moved straight to Salt Lake City + after we got married to build Rocky Mountain Vending. +

+

+ Our focus is on healthy vending and dependable service, while + still helping locations that want traditional snack and drink + options too. +

+

+ Rocky Mountain Vending is a local family-run business founded in + 2019. We believe business should be built on trust, exceptional + customer service, modern technology, and enjoying the work we + do. +

+
+ + +

+ If you're looking for a more personal vending partner with + modern tools and real follow-through, that's exactly what + we set out to build. +

+

+ ~Matt +

+
+
-
+
) } - diff --git a/components/contact-page.tsx b/components/contact-page.tsx index 76465a01..6889eba5 100644 --- a/components/contact-page.tsx +++ b/components/contact-page.tsx @@ -2,8 +2,11 @@ import { Clock, Mail, Phone } from "lucide-react" import { ContactForm } from "@/components/forms/contact-form" -import { PublicInset, PublicPageHeader, PublicSurface } from "@/components/public-surface" -import { Card, CardContent } from "@/components/ui/card" +import { + PublicInset, + PublicPageHeader, + PublicSurface, +} from "@/components/public-surface" import { businessConfig } from "@/lib/seo-config" export function ContactPage() { @@ -18,7 +21,7 @@ export function ContactPage() { ] return ( -
+
Contact Form
-

For repairs or moving, include the machine model and a clear description of what's happening.

+

+ For repairs or moving, include the machine model and a clear + description of what's happening. +

- console.log("Contact form submitted:", data)} /> + console.log("Contact form submitted:", data)} + />
diff --git a/components/footer.tsx b/components/footer.tsx index 47f5f860..6d91b81b 100644 --- a/components/footer.tsx +++ b/components/footer.tsx @@ -1,17 +1,25 @@ import Link from "next/link" import Image from "next/image" -import { Facebook, Twitter, Linkedin, Youtube } from "lucide-react" +import { + Facebook, + Globe, + Linkedin, + Mail, + Phone, + Twitter, + Youtube, +} from "lucide-react" import { Separator } from "./ui/separator" export function Footer() { const currentYear = new Date().getFullYear() return ( -