Rocky_Mountain_Vending/components/location-landing-page.tsx

563 lines
23 KiB
TypeScript

import type { Metadata } from "next"
import Link from "next/link"
import {
ArrowRight,
Clock,
Globe,
Mail,
MapPin,
Phone,
Wrench,
} from "lucide-react"
import { Breadcrumbs } from "@/components/breadcrumbs"
import { ReviewsSection } from "@/components/reviews-section"
import { GetFreeMachineCta } from "@/components/get-free-machine-cta"
import {
PublicInset,
PublicPageHeader,
PublicSurface,
} from "@/components/public-surface"
import type { LocationData } from "@/lib/location-data"
import { generateSEOMetadata } from "@/lib/seo"
import { buildAbsoluteUrl, buildLocationRoute } from "@/lib/seo-registry"
import { businessConfig } from "@/lib/seo-config"
const SALT_LAKE_COUNTY = new Set([
"salt-lake-city-utah",
"sandy-utah",
"draper-utah",
"murray-utah",
"midvale-utah",
"south-salt-lake-utah",
"west-valley-city-utah",
"west-jordan-utah",
"south-jordan-utah",
"riverton-utah",
"herriman-utah",
"holladay-utah",
"millcreek-utah",
"cottonwood-heights-utah",
])
const DAVIS_COUNTY = new Set([
"ogden-utah",
"layton-utah",
"clearfield-utah",
"syracuse-utah",
"clinton-utah",
])
function getCountyName(locationSlug: string) {
if (SALT_LAKE_COUNTY.has(locationSlug)) {
return "Salt Lake County"
}
if (DAVIS_COUNTY.has(locationSlug)) {
return "Davis County"
}
return "Utah County"
}
function getIndustryFocus(locationData: LocationData) {
const county = getCountyName(locationData.slug)
const countyIndustries: Record<string, string[]> = {
"Salt Lake County": [
"offices",
"warehouses",
"gyms",
"schools",
"service businesses",
],
"Davis County": [
"warehouses",
"auto repair shops",
"community centers",
"schools",
"offices",
],
"Utah County": [
"offices",
"training spaces",
"schools",
"fitness locations",
"community facilities",
],
}
return Array.from(
new Set([locationData.anecdote.customer, ...countyIndustries[county]])
).slice(0, 5)
}
export function generateLocationPageMetadata(
locationData: LocationData
): Metadata {
return generateSEOMetadata({
title: `Vending Machines in ${locationData.city}, ${locationData.stateAbbr}`,
description: `Rocky Mountain Vending provides free placement for qualifying locations, machine sales, repairs, and vending service for businesses in ${locationData.city}, ${locationData.stateAbbr}. Explore local coverage and contact options.`,
path: buildLocationRoute(locationData.slug),
keywords: [
`vending machines ${locationData.city.toLowerCase()}`,
`vending machine repair ${locationData.city.toLowerCase()}`,
`vending service ${locationData.city.toLowerCase()}`,
`${locationData.city.toLowerCase()} vending company`,
...locationData.neighborhoods.map(
(neighborhood) => `vending machines ${neighborhood.toLowerCase()}`
),
],
})
}
export function LocationLandingPage({
locationData,
}: {
locationData: LocationData
}) {
const isSaltLakeCity = locationData.slug === "salt-lake-city-utah"
const countyName = getCountyName(locationData.slug)
const industries = getIndustryFocus(locationData)
const canonicalUrl = buildAbsoluteUrl(buildLocationRoute(locationData.slug))
const title = `Vending Machines in ${locationData.city}, ${locationData.stateAbbr}`
const description = `Rocky Mountain Vending provides free placement for qualifying locations, machine sales, repairs, and vending service for businesses in ${locationData.city}, ${locationData.stateAbbr}.`
const comparisonRows = [
["Credit card readers", "Yes", "Maybe", "Yes"],
["Locally owned", "Yes", "Yes", "Maybe"],
["Fast service", "Yes", "Maybe", "Maybe"],
["Large selection of products", "Yes", "Maybe", "Yes"],
["Quality of equipment used", "Excellent", "Varies", "Excellent"],
["Locked into Coke or Pepsi equipment", "No", "Maybe", "Probably"],
]
const saltLakeServiceLinks = [
{
title: "Traditional snacks and drinks",
body: "Stock the machine with the classic snacks, sodas, and convenience items most locations still want every day.",
href: "/food-and-beverage/traditional-options",
},
{
title: "Healthy snacks and drinks",
body: "Offer protein bars, better-for-you snacks, and drink choices that fit health-conscious teams and customers.",
href: "/food-and-beverage/healthy-options",
},
{
title: "Snack and drink delivery",
body: "Need product delivery or a broader refreshment setup beyond standard machine placement? We can help there too.",
href: "/food-and-beverage/snack-and-drink-delivery",
},
{
title: "Vending machine sales",
body: "Compare purchase options if you want equipment ownership instead of a free-placement arrangement.",
href: "/vending-machines/machines-for-sale",
},
{
title: "Parts, repairs, and moving",
body: "Get support for repair work, machine moving, replacement parts, and operational issues that need direct service help.",
href: "/services/parts",
},
{
title: "Training and support",
body: "Browse the support pages and machine guides if you need help with specific models, manuals, or machine operation questions.",
href: "/blog",
},
]
const structuredData = {
"@context": "https://schema.org",
"@graph": [
{
"@type": "WebPage",
name: title,
description,
url: canonicalUrl,
isPartOf: {
"@type": "WebSite",
name: businessConfig.name,
url: businessConfig.website,
},
},
{
"@type": "Service",
name: `${businessConfig.name} vending services in ${locationData.city}, ${locationData.stateAbbr}`,
description,
url: canonicalUrl,
serviceType:
"Free vending machine placement, vending machine sales, repairs, parts, moving, and ongoing service",
provider: {
"@type": "Organization",
name: businessConfig.name,
url: businessConfig.website,
telephone: businessConfig.phoneFormatted,
email: businessConfig.email,
},
areaServed: {
"@type": "City",
name: locationData.city,
containedInPlace: {
"@type": "AdministrativeArea",
name: countyName,
},
address: {
"@type": "PostalAddress",
addressLocality: locationData.city,
addressRegion: locationData.stateAbbr,
postalCode: locationData.zipCode,
addressCountry: "US",
},
},
},
],
}
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
/>
<article className="container mx-auto px-4 py-10 md:py-14">
<Breadcrumbs
className="mb-6"
items={[
{ label: "Service Areas", href: "/service-areas" },
{
label: `${locationData.city}, ${locationData.stateAbbr}`,
href: buildLocationRoute(locationData.slug),
},
]}
/>
<PublicPageHeader
align="center"
eyebrow="Local Service Area"
title={`Vending machine service for businesses in ${locationData.city}, ${locationData.stateAbbr}`}
description={`Rocky Mountain Vending helps businesses in ${locationData.city} with free placement for qualifying locations, machine sales, repairs, parts, and ongoing service across ${countyName} and nearby communities.`}
className="mb-12 md:mb-16"
/>
<div className="mx-auto mb-14 grid max-w-5xl gap-6 lg:grid-cols-[1.08fr_0.92fr]">
<PublicSurface className="p-6 md:p-8">
<h2 className="text-2xl font-semibold tracking-tight text-balance">
A local vending partner for businesses across {locationData.city}
</h2>
<p className="mt-4 text-base leading-relaxed text-muted-foreground">
If your business is in {locationData.neighborhoods.join(", ")}, or
elsewhere in {locationData.city}, we can review the location,
recommend the right machine mix, and explain what service would
look like once the machines are in place.
</p>
<p className="mt-4 text-base leading-relaxed text-muted-foreground">
We regularly work with businesses in {countyName} that want snack,
beverage, combo, or healthier vending options without asking their
own staff to handle stocking, service calls, or day-to-day machine
issues.
</p>
</PublicSurface>
<PublicSurface className="p-6 md:p-8">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
Common Location Types
</p>
<h2 className="mt-3 text-2xl font-semibold tracking-tight text-balance">
Common business types we serve in {locationData.city}
</h2>
<div className="mt-5 flex flex-wrap gap-2.5">
{industries.map((industry) => (
<span
key={industry}
className="rounded-full border border-border/60 bg-background px-3 py-1.5 text-sm text-muted-foreground"
>
{industry}
</span>
))}
</div>
<PublicInset className="mt-6">
<p className="text-sm leading-relaxed text-muted-foreground">
Looking for coverage beyond {locationData.city}? We also work in{" "}
{locationData.nearbyCities.join(", ")} and across our broader
Utah service area.
</p>
</PublicInset>
<div className="mt-6 flex flex-col gap-3 sm:flex-row">
<GetFreeMachineCta buttonLabel="Check Placement Fit" />
<Link
href="/contact-us#contact-form"
className="inline-flex min-h-11 items-center justify-center rounded-full border border-border bg-white px-4 text-sm font-medium text-foreground transition hover:border-primary/40 hover:text-primary"
>
Talk to Our Team
</Link>
</div>
</PublicSurface>
</div>
<section className="mx-auto mb-16 max-w-5xl">
<h2 className="text-3xl font-bold tracking-tight text-balance">
Vending services available in {locationData.city}
</h2>
<div className="mt-8 grid gap-5 md:grid-cols-2">
{[
{
title: "Free vending placement",
body: "For qualifying locations, we review traffic, layout, and product demand before recommending placement and ongoing service.",
href: "/",
cta: "Start a placement request",
},
{
title: "Machine sales and upgrades",
body: "If you want to buy equipment, we can help you compare machine types, payment options, and layouts that work for the way your location operates.",
href: "/vending-machines",
cta: "See machine options",
},
{
title: "Repairs and troubleshooting",
body: "We help with machine issues, maintenance needs, and day-to-day operating problems throughout our Utah service area.",
href: "/services/repairs",
cta: "Explore repair service",
},
{
title: "Parts, manuals, and moving help",
body: "We also help with parts sourcing, manuals, and machine moving when your location needs more than routine vending service.",
href: "/services/parts",
cta: "View manuals and parts",
},
].map((service) => (
<PublicSurface key={service.title} className="h-full p-6 md:p-7">
<h3 className="text-xl font-semibold">{service.title}</h3>
<p className="mt-3 leading-7 text-muted-foreground">
{service.body}
</p>
<Link
href={service.href}
className="mt-5 inline-flex items-center gap-2 text-sm font-medium text-primary hover:underline"
>
{service.cta}
<ArrowRight className="h-4 w-4" />
</Link>
</PublicSurface>
))}
</div>
</section>
{isSaltLakeCity ? (
<section className="mx-auto mb-16 max-w-5xl space-y-6">
<PublicSurface className="p-6 md:p-8">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
Why Rocky Mountain Vending
</p>
<h2 className="mt-3 text-3xl font-bold tracking-tight text-balance">
What Salt Lake City businesses usually want to verify before they choose a vendor.
</h2>
<p className="mt-4 text-base leading-relaxed text-muted-foreground">
Most businesses care about the same things: service speed,
product flexibility, local ownership, and whether the machines
feel modern and dependable after install.
</p>
<div className="mt-6 overflow-hidden rounded-[1.5rem] border border-border/60">
<div className="overflow-x-auto">
<table className="w-full min-w-[680px] border-collapse text-sm">
<thead className="bg-muted/55">
<tr className="border-b border-border/60">
<th className="px-4 py-3 text-left font-semibold text-foreground">
Comparison point
</th>
<th className="px-4 py-3 text-left font-semibold text-foreground">
Rocky Mountain Vending
</th>
<th className="px-4 py-3 text-left font-semibold text-foreground">
Small Vendor
</th>
<th className="px-4 py-3 text-left font-semibold text-foreground">
Large Vendor
</th>
</tr>
</thead>
<tbody>
{comparisonRows.map((row, index) => (
<tr
key={row[0]}
className={`border-b border-border/50 ${index % 2 === 0 ? "bg-background" : "bg-muted/20"}`}
>
<td className="px-4 py-3 font-medium text-foreground">
{row[0]}
</td>
<td className="px-4 py-3 text-muted-foreground">
{row[1]}
</td>
<td className="px-4 py-3 text-muted-foreground">
{row[2]}
</td>
<td className="px-4 py-3 text-muted-foreground">
{row[3]}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</PublicSurface>
<PublicSurface className="p-6 md:p-8">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
Salt Lake City Services
</p>
<h2 className="mt-3 text-3xl font-bold tracking-tight text-balance">
The service paths Salt Lake City businesses usually ask about first.
</h2>
<div className="mt-6 grid gap-4 md:grid-cols-2">
{saltLakeServiceLinks.map((item) => (
<PublicInset key={item.title} className="flex h-full flex-col">
<h3 className="text-lg font-semibold text-foreground">
{item.title}
</h3>
<p className="mt-2 flex-1 text-sm leading-relaxed text-muted-foreground">
{item.body}
</p>
<Link
href={item.href}
className="mt-4 inline-flex items-center gap-2 text-sm font-medium text-primary hover:underline"
>
Learn more
<ArrowRight className="h-4 w-4" />
</Link>
</PublicInset>
))}
</div>
</PublicSurface>
</section>
) : null}
<section className="mx-auto mb-16 grid max-w-5xl gap-6 lg:grid-cols-[1.05fr_0.95fr]">
<PublicSurface className="p-6 md:p-8">
<h2 className="text-3xl font-bold tracking-tight text-balance">
Coverage around {locationData.city}
</h2>
<p className="mt-4 text-base leading-relaxed text-muted-foreground">
We serve businesses throughout {locationData.city} and nearby
areas such as {locationData.nearbyCities.join(", ")}. If your
location is nearby but not listed, our team can confirm whether
the site fits our current route coverage.
</p>
<ul className="mt-6 space-y-3 text-sm text-muted-foreground">
<li className="flex items-start gap-3">
<MapPin className="mt-0.5 h-4 w-4 flex-shrink-0 text-primary" />
Neighborhood coverage includes{" "}
{locationData.neighborhoods.join(", ")}.
</li>
<li className="flex items-start gap-3">
<Globe className="mt-0.5 h-4 w-4 flex-shrink-0 text-primary" />
City reference:{" "}
<a
href={locationData.cityWebsite}
target="_blank"
rel="noopener noreferrer"
className="underline decoration-primary/35 underline-offset-4 hover:decoration-primary"
>
{locationData.cityWebsite.replace(/^https?:\/\//, "")}
</a>
</li>
<li className="flex items-start gap-3">
<Globe className="mt-0.5 h-4 w-4 flex-shrink-0 text-primary" />
Business network:{" "}
<a
href={locationData.chamberUrl}
target="_blank"
rel="noopener noreferrer"
className="underline decoration-primary/35 underline-offset-4 hover:decoration-primary"
>
{locationData.chamberName}
</a>
</li>
</ul>
<Link
href="/service-areas"
className="mt-6 inline-flex items-center gap-2 text-sm font-medium text-primary hover:underline"
>
View all Utah service areas
<ArrowRight className="h-4 w-4" />
</Link>
</PublicSurface>
<PublicSurface className="p-6 md:p-8">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">
Contact Rocky Mountain Vending
</p>
<h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance">
Reach out about a {locationData.city} location
</h2>
<div className="mt-6 grid gap-4">
<PublicInset className="flex items-start gap-4">
<Phone className="mt-1 h-5 w-5 flex-shrink-0 text-primary" />
<div>
<div className="font-semibold text-foreground">Call</div>
<a
href={businessConfig.phoneUrl}
className="text-muted-foreground hover:underline"
>
{businessConfig.phone}
</a>
</div>
</PublicInset>
<PublicInset className="flex items-start gap-4">
<Mail className="mt-1 h-5 w-5 flex-shrink-0 text-primary" />
<div>
<div className="font-semibold text-foreground">Email</div>
<a
href={`mailto:${businessConfig.email}`}
className="text-muted-foreground hover:underline"
>
{businessConfig.email}
</a>
</div>
</PublicInset>
<PublicInset className="flex items-start gap-4">
<Clock className="mt-1 h-5 w-5 flex-shrink-0 text-primary" />
<div>
<div className="font-semibold text-foreground">Hours</div>
<p className="text-muted-foreground">
Monday through Friday, 8:00 AM to 5:00 PM
</p>
</div>
</PublicInset>
<PublicInset className="flex items-start gap-4">
<Wrench className="mt-1 h-5 w-5 flex-shrink-0 text-primary" />
<div>
<div className="font-semibold text-foreground">
Service questions
</div>
<p className="text-muted-foreground">
Ask about placement, repairs, moving, parts, or machine
sales for your {locationData.city} business.
</p>
</div>
</PublicInset>
</div>
</PublicSurface>
</section>
<PublicSurface className="mx-auto max-w-4xl p-6 md:p-8">
<div className="text-center">
<h2 className="text-3xl font-bold tracking-tight text-balance">
Request vending service for your {locationData.city} location
</h2>
<p className="mx-auto mt-4 max-w-2xl text-base leading-relaxed text-muted-foreground">
Tell us about your space, expected traffic, and the type of
vending help you need. We&apos;ll follow up with the next best
option for your location.
</p>
<div className="mt-6 flex flex-col items-center gap-3 sm:flex-row sm:justify-center">
<GetFreeMachineCta buttonLabel="See If Your Location Qualifies" />
<Link
href="/contact-us#contact-form"
className="inline-flex min-h-11 items-center justify-center rounded-full border border-border bg-white px-4 text-sm font-medium text-foreground transition hover:border-primary/40 hover:text-primary"
>
Talk to Our Team
</Link>
</div>
</div>
</PublicSurface>
</article>
<ReviewsSection />
</>
)
}