Rocky_Mountain_Vending/components/location-landing-page.tsx

413 lines
16 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 { Card, CardContent } from "@/components/ui/card"
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 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 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={`${locationData.city}, ${locationData.stateAbbr} vending machine service`}
description={`Rocky Mountain Vending serves businesses in ${locationData.city}, ${locationData.stateAbbr} with free placement for qualifying locations, machine sales, repairs, parts, and ongoing restocking and service across ${countyName} and nearby communities.`}
className="mb-12 md:mb-16"
/>
<div className="mx-auto mb-12 grid max-w-5xl gap-6 lg:grid-cols-[1.1fr_0.9fr]">
<PublicSurface className="p-6 md:p-8">
<h2 className="text-2xl font-semibold tracking-tight text-balance">
Vending service 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">
{industries.map((industry) => (
<span
key={industry}
className="rounded-full border border-border/60 bg-background px-3 py-1 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>
</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-6 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) => (
<Card key={service.title} className="h-full">
<CardContent className="p-6">
<h3 className="text-xl font-semibold">{service.title}</h3>
<p className="mt-3 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>
</CardContent>
</Card>
))}
</div>
</section>
<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">
<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 />
</>
)
}