deploy: reset public forms and white surfaces

This commit is contained in:
DMleadgen 2026-03-27 15:57:59 -06:00
parent e2124cfa66
commit 7160259afd
Signed by: matt
GPG key ID: C2720CF8CD701894
25 changed files with 239 additions and 191 deletions

View file

@ -18,7 +18,7 @@ import { ContactPage } from '@/components/contact-page';
import { AboutPage } from '@/components/about-page'; import { AboutPage } from '@/components/about-page';
import { WhoWeServePage } from '@/components/who-we-serve-page'; import { WhoWeServePage } from '@/components/who-we-serve-page';
import { PublicPageHeader, PublicSurface } from '@/components/public-surface'; import { PublicPageHeader, PublicSurface } from '@/components/public-surface';
import { RequestMachineForm } from '@/components/forms/request-machine-form'; import { GetFreeMachineCta } from '@/components/get-free-machine-cta';
// Required for static export - ensures this route is statically generated // Required for static export - ensures this route is statically generated
export const dynamic = 'force-static'; export const dynamic = 'force-static';
@ -346,17 +346,23 @@ function renderLocationPage(locationData: any, locationSlug: string) {
</Card> </Card>
</div> </div>
{/* CRM Form */} {/* Placement CTA */}
<PublicSurface> <PublicSurface>
<CardContent className="p-1"> <div className="p-1 text-center">
<div className="text-center mb-6">
<h3 className="text-2xl font-bold mb-2">Get Your Free Vending Machine</h3> <h3 className="text-2xl font-bold mb-2">Get Your Free Vending Machine</h3>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Tell us about your location and we'll follow up within one business day. Open the Rocky placement popup and we&apos;ll follow up within one business day with the best next step for your location.
</p> </p>
<div className="mt-6 flex flex-col items-center gap-3">
<GetFreeMachineCta buttonLabel="Open Free Placement Form" />
<a
href={businessConfig.publicCallUrl}
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"
>
Call Instead
</a>
</div>
</div> </div>
<RequestMachineForm />
</CardContent>
</PublicSurface> </PublicSurface>
</section> </section>
@ -404,9 +410,7 @@ function renderLocationPage(locationData: any, locationSlug: string) {
</div> </div>
<div className="text-center"> <div className="text-center">
<Button asChild size="lg" className="rounded-full bg-primary px-6 hover:bg-primary/90"> <GetFreeMachineCta buttonLabel="Get Your Free Machine Today" />
<Link href="#contact">Get Your Free Machine Today</Link>
</Button>
</div> </div>
</section> </section>
</article> </article>

View file

@ -6,6 +6,7 @@ import { businessConfig } from "@/lib/seo-config"
import { MapPin, Phone, ArrowRight, Wrench, Clock } from "lucide-react" import { MapPin, Phone, ArrowRight, Wrench, Clock } from "lucide-react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { PublicInset, PublicPageHeader, PublicSurface } from "@/components/public-surface" import { PublicInset, PublicPageHeader, PublicSurface } from "@/components/public-surface"
import { GetFreeMachineCta } from "@/components/get-free-machine-cta"
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Service Areas | Vending Machines Across Utah | Rocky Mountain Vending", title: "Service Areas | Vending Machines Across Utah | Rocky Mountain Vending",
@ -135,9 +136,7 @@ export default function ServiceAreasPage() {
</a> </a>
<p className="mt-2 text-sm text-muted-foreground">We&apos;ll confirm delivery range, support availability, and the best intake path for your location.</p> <p className="mt-2 text-sm text-muted-foreground">We&apos;ll confirm delivery range, support availability, and the best intake path for your location.</p>
</PublicInset> </PublicInset>
<Button asChild className="h-11 rounded-full px-5"> <GetFreeMachineCta buttonLabel="Request a Free Machine" className="h-11 px-5" />
<Link href="/#request-machine">Request a Free Machine</Link>
</Button>
</div> </div>
</PublicSurface> </PublicSurface>
</div> </div>
@ -247,9 +246,9 @@ export default function ServiceAreasPage() {
</PublicInset> </PublicInset>
))} ))}
</div> </div>
<Button asChild className="mt-6 h-11 rounded-full px-5"> <div className="mt-6">
<Link href="/#request-machine">Get Your Free Machine</Link> <GetFreeMachineCta buttonLabel="Get Your Free Machine" className="h-11 px-5" />
</Button> </div>
</PublicSurface> </PublicSurface>
<PublicSurface> <PublicSurface>

View file

@ -2,10 +2,10 @@ import type { Metadata } from "next";
import Image from "next/image"; import Image from "next/image";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { PageWrapper, PageHeader } from "@/components/page-wrapper";
import { businessConfig } from "@/lib/seo-config"; import { businessConfig } from "@/lib/seo-config";
import { Phone, CheckCircle2, Shield, Clock, MapPin } from "lucide-react"; import { Phone, CheckCircle2, Shield, Clock, MapPin } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { PublicPageHeader, PublicSurface } from "@/components/public-surface";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Vending Machine Moving & Relocation Services | Rocky Mountain Vending", title: "Vending Machine Moving & Relocation Services | Rocky Mountain Vending",
@ -32,10 +32,12 @@ export const metadata: Metadata = {
export default function MovingServicesPage() { export default function MovingServicesPage() {
return ( return (
<PageWrapper maxWidth="6xl"> <div className="container mx-auto max-w-6xl px-4 py-10 md:py-14">
<PageHeader <PublicPageHeader
align="center"
eyebrow="Moving Services"
title="Vending Machine Moving & Relocation Services" title="Vending Machine Moving & Relocation Services"
subtitle="Professional Vending Machine Moving in Utah" description="Professional vending machine moving in Utah for snack, beverage, and combo machines, with careful transport, safer handling, and cleaner scheduling from pickup to placement."
/> />
{/* Introduction Section */} {/* Introduction Section */}
@ -248,7 +250,7 @@ export default function MovingServicesPage() {
{/* Why Choose Us Section */} {/* Why Choose Us Section */}
<section className="mb-12"> <section className="mb-12">
<h2 className="text-3xl font-bold mb-8 tracking-tight text-balance">Why Choose Us for Your Vending Move?</h2> <h2 className="text-3xl font-bold mb-8 tracking-tight text-balance">Why Choose Us for Your Vending Move?</h2>
<Card className="border-secondary/20 bg-secondary/5"> <Card className="border-border/70">
<CardContent className="p-6 md:p-8"> <CardContent className="p-6 md:p-8">
<ul className="space-y-4"> <ul className="space-y-4">
<li className="flex items-start gap-3"> <li className="flex items-start gap-3">
@ -291,29 +293,27 @@ export default function MovingServicesPage() {
{/* CTA Section */} {/* CTA Section */}
<section className="mb-12"> <section className="mb-12">
<Card className="border-2 shadow-lg [&>div]:!bg-transparent" style={{ background: 'linear-gradient(to right, var(--link-hover-color), var(--link-hover-color-dark))' }}> <PublicSurface className="p-10 text-center md:p-12">
<CardContent className="p-10 md:p-12 text-center"> <h2 className="text-2xl md:text-3xl font-bold text-foreground mb-4 tracking-tight text-balance">
<h2 className="text-2xl md:text-3xl font-bold text-white mb-4 tracking-tight text-balance">
Ready to Schedule a Hassle-Free Vending Machine Move? Ready to Schedule a Hassle-Free Vending Machine Move?
</h2> </h2>
<p className="text-white/90 mb-8 max-w-2xl mx-auto text-lg leading-relaxed"> <p className="text-muted-foreground mb-8 max-w-2xl mx-auto text-lg leading-relaxed">
Contact us today for a custom quote based on machine type, pickup/drop-off locations, access challenges, Contact us today for a custom quote based on machine type, pickup/drop-off locations, access challenges,
and any stairs or special requirements involved. We're here to make your relocation simple and stress-free! and any stairs or special requirements involved. We're here to make your relocation simple and stress-free!
</p> </p>
<div className="flex flex-col sm:flex-row gap-4 justify-center"> <div className="flex flex-col sm:flex-row gap-4 justify-center">
<Button asChild size="lg" className="bg-white text-[var(--link-hover-color)] hover:bg-white/90 text-lg h-12 px-8 font-semibold"> <Button asChild size="lg" className="text-lg h-12 px-8 font-semibold rounded-full">
<Link href="/#contact">Request a Free Quote</Link> <Link href="/contact-us#contact-form">Request a Quote</Link>
</Button> </Button>
<Button asChild size="lg" className="bg-secondary text-white hover:bg-secondary/90 text-lg h-12 px-8 font-semibold"> <Button asChild size="lg" variant="outline" className="text-lg h-12 px-8 font-semibold rounded-full">
<a href={businessConfig.phoneUrl} className="flex items-center gap-2"> <a href={businessConfig.phoneUrl} className="flex items-center gap-2">
<Phone className="w-5 h-5" /> <Phone className="w-5 h-5" />
{businessConfig.phone} {businessConfig.phone}
</a> </a>
</Button> </Button>
</div> </div>
</CardContent> </PublicSurface>
</Card>
</section> </section>
</PageWrapper> </div>
); );
} }

View file

@ -7,7 +7,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { ReviewsSection } from "@/components/reviews-section"; import { ReviewsSection } from "@/components/reviews-section";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import Link from "next/link"; import Link from "next/link";
import { RequestMachineForm } from "@/components/forms/request-machine-form"; import { GetFreeMachineCta } from "@/components/get-free-machine-cta";
import { PublicInset, PublicPageHeader, PublicSurface } from "@/components/public-surface"; import { PublicInset, PublicPageHeader, PublicSurface } from "@/components/public-surface";
interface LocationPageProps { interface LocationPageProps {
@ -346,19 +346,27 @@ export default async function LocationPage({ params }: LocationPageProps) {
</Card> </Card>
</div> </div>
{/* CRM Form */} {/* Placement CTA */}
<PublicSurface className="p-6 md:p-8"> <PublicSurface className="p-6 md:p-8">
<div className="text-center mb-6"> <div className="text-center mb-6">
<h3 className="text-2xl font-bold mb-2">Get Your Free Vending Machine</h3> <h3 className="text-2xl font-bold mb-2">Get Your Free Vending Machine</h3>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Tell us about your location and we&apos;ll follow up within one business day to talk through the best machine mix. Open the free placement popup and we&apos;ll follow up within one business day to talk through the best machine mix for your location.
</p> </p>
</div> </div>
<PublicInset className="mb-5 p-5 text-sm leading-relaxed text-muted-foreground"> <PublicInset className="mb-5 p-5 text-sm leading-relaxed text-muted-foreground">
Free placement is for qualifying business locations. Share your foot traffic, preferred machine types, Free placement is for qualifying business locations. Share your foot traffic, preferred machine types,
and any site constraints so our team can recommend the right setup. and any site constraints so our team can recommend the right setup.
</PublicInset> </PublicInset>
<RequestMachineForm /> <div className="flex flex-col items-center gap-3">
<GetFreeMachineCta buttonLabel="Open Free Placement Form" />
<a
href={businessConfig.publicCallUrl}
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"
>
Call Instead
</a>
</div>
</PublicSurface> </PublicSurface>
</section> </section>
@ -406,9 +414,7 @@ export default async function LocationPage({ params }: LocationPageProps) {
</div> </div>
<div className="text-center"> <div className="text-center">
<Button asChild size="lg" className="bg-primary hover:bg-primary/90"> <GetFreeMachineCta buttonLabel="Get Your Free Machine Today" />
<Link href="#contact">Get Your Free Machine Today</Link>
</Button>
</div> </div>
</section> </section>
</article> </article>

View file

@ -5,7 +5,7 @@ import { getPageBySlug } from '@/lib/wordpress-data-loader'
import { cleanWordPressContent } from '@/lib/clean-wordPress-content' import { cleanWordPressContent } from '@/lib/clean-wordPress-content'
import type { Metadata } from 'next' import type { Metadata } from 'next'
import { PublicPageHeader, PublicSurface } from '@/components/public-surface' import { PublicPageHeader, PublicSurface } from '@/components/public-surface'
import { RequestMachineForm } from '@/components/forms/request-machine-form' import { GetFreeMachineCta } from '@/components/get-free-machine-cta'
const WORDPRESS_SLUG = 'vending-machines-for-sale-in-utah' const WORDPRESS_SLUG = 'vending-machines-for-sale-in-utah'
@ -83,11 +83,20 @@ export default async function MachinesForSalePage() {
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">Free Placement</p> <p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">Free Placement</p>
<h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance">Need a free machine instead of buying one?</h2> <h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance">Need a free machine instead of buying one?</h2>
<p className="mt-3 text-base leading-relaxed text-muted-foreground"> <p className="mt-3 text-base leading-relaxed text-muted-foreground">
If you&apos;re a business looking for placement rather than a purchase, use the request form here and we&apos;ll help you sort out the right next step. If you&apos;re a business looking for placement rather than a purchase, open the free placement popup and we&apos;ll help you sort out the right next step.
</p> </p>
<div className="mt-6">
<GetFreeMachineCta buttonLabel="Open Free Placement Form" />
</div>
</PublicSurface> </PublicSurface>
<PublicSurface> <PublicSurface className="flex items-center justify-center text-center">
<RequestMachineForm /> <div className="max-w-xl">
<p className="text-sm font-semibold uppercase tracking-[0.18em] text-primary/80">Need Sales Help?</p>
<h3 className="mt-3 text-2xl font-semibold tracking-tight text-balance">Talk through machine sales, placement, or feature questions.</h3>
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
We can help with new vs. used options, payment hardware, and whether free placement or a direct purchase makes more sense for your location.
</p>
</div>
</PublicSurface> </PublicSurface>
</section> </section>
</div> </div>

View file

@ -3,7 +3,7 @@ import { VendingMachinesShowcase } from '@/components/vending-machines-showcase'
import { FeatureCard } from '@/components/feature-card' import { FeatureCard } from '@/components/feature-card'
import type { Metadata } from 'next' import type { Metadata } from 'next'
import { PublicPageHeader, PublicSurface } from '@/components/public-surface' import { PublicPageHeader, PublicSurface } from '@/components/public-surface'
import { RequestMachineForm } from '@/components/forms/request-machine-form' import { GetFreeMachineCta } from '@/components/get-free-machine-cta'
export async function generateMetadata(): Promise<Metadata> { export async function generateMetadata(): Promise<Metadata> {
return generateSEOMetadata({ return generateSEOMetadata({
@ -88,10 +88,10 @@ export default async function MachinesWeUsePage() {
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">Free Placement</p> <p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary/80">Free Placement</p>
<h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance">Want this kind of setup at your location?</h2> <h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance">Want this kind of setup at your location?</h2>
<p className="mt-3 text-base leading-relaxed text-muted-foreground"> <p className="mt-3 text-base leading-relaxed text-muted-foreground">
Use the request form here if you want free placement for a qualifying business and we&apos;ll help map out the best machine mix. If you want free placement for a qualifying business, open the Rocky intake popup and we&apos;ll help map out the best machine mix without dropping the full form into this page.
</p> </p>
<div className="mt-6"> <div className="mt-6 flex flex-wrap gap-3">
<RequestMachineForm /> <GetFreeMachineCta buttonLabel="Open Free Placement Form" />
</div> </div>
</PublicSurface> </PublicSurface>
</section> </section>

View file

@ -38,8 +38,8 @@ export function ContactPage() {
</PublicSurface> </PublicSurface>
<aside className="space-y-5"> <aside className="space-y-5">
<Card className="overflow-hidden rounded-[2rem] border-border/70 bg-background shadow-[0_20px_50px_rgba(0,0,0,0.08)]"> <Card className="overflow-hidden rounded-[2rem] border-border/70 bg-white shadow-[0_20px_50px_rgba(0,0,0,0.08)]">
<CardContent className="bg-background p-6"> <CardContent className="bg-white p-6">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-primary/80">Direct Options</p> <p className="text-xs font-semibold uppercase tracking-[0.18em] text-primary/80">Direct Options</p>
<h2 className="mt-2 text-2xl font-semibold text-foreground">Reach the team directly</h2> <h2 className="mt-2 text-2xl font-semibold text-foreground">Reach the team directly</h2>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground"> <p className="mt-2 text-sm leading-relaxed text-muted-foreground">
@ -47,7 +47,7 @@ export function ContactPage() {
</p> </p>
<div className="mt-6 space-y-4"> <div className="mt-6 space-y-4">
<a href={businessConfig.publicCallUrl} className="flex items-start gap-4 rounded-2xl border border-border/60 bg-background px-4 py-4 transition hover:border-primary/35"> <a href={businessConfig.publicCallUrl} className="flex items-start gap-4 rounded-2xl border border-border/60 bg-white px-4 py-4 transition hover:border-primary/35">
<div className="flex h-11 w-11 items-center justify-center rounded-full bg-primary/10 text-primary"> <div className="flex h-11 w-11 items-center justify-center rounded-full bg-primary/10 text-primary">
<Phone className="h-5 w-5" /> <Phone className="h-5 w-5" />
</div> </div>
@ -58,7 +58,7 @@ export function ContactPage() {
</div> </div>
</a> </a>
<a href={`mailto:${businessConfig.email}?Subject=Rocky%20Mountain%20Vending%20Inquiry`} className="flex items-start gap-4 rounded-2xl border border-border/60 bg-background px-4 py-4 transition hover:border-primary/35"> <a href={`mailto:${businessConfig.email}?Subject=Rocky%20Mountain%20Vending%20Inquiry`} className="flex items-start gap-4 rounded-2xl border border-border/60 bg-white px-4 py-4 transition hover:border-primary/35">
<div className="flex h-11 w-11 items-center justify-center rounded-full bg-primary/10 text-primary"> <div className="flex h-11 w-11 items-center justify-center rounded-full bg-primary/10 text-primary">
<Mail className="h-5 w-5" /> <Mail className="h-5 w-5" />
</div> </div>

View file

@ -1,65 +1,88 @@
"use client" "use client"
import { Phone, MapPin } from "lucide-react" import Link from "next/link"
import { ContactForm } from "@/components/forms/contact-form" import { MapPin, Phone, Wrench } from "lucide-react"
import { PublicInset, PublicPageHeader, PublicSurface } from "@/components/public-surface" import { PublicInset, PublicPageHeader, PublicSurface } from "@/components/public-surface"
import { businessConfig } from "@/lib/seo-config" import { businessConfig } from "@/lib/seo-config"
export function ContactSection() { export function ContactSection() {
return ( return (
<section id="contact" className="py-20 md:py-28 bg-muted/30"> <section id="contact" className="py-20 md:py-28 bg-muted/30">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<PublicPageHeader <PublicPageHeader
className="mb-10 max-w-3xl" className="mb-10 max-w-3xl"
eyebrow="Detailed Questions" eyebrow="Need Something Else?"
title="Need something more specific? We can sort that out here." title="Repairs, moving, manuals, and sales questions all funnel through one clean contact path."
description="If you already handled the quick placement request, use this longer form for service details, sales questions, or anything that needs more context." description="Instead of repeating the full intake form on every page, we keep the detailed service request on the dedicated contact page and use this section as the quick handoff."
/> />
<div className="grid gap-12 lg:grid-cols-2 items-start"> <div className="grid gap-6 lg:grid-cols-[1.05fr_0.95fr] lg:items-start">
<PublicSurface className="space-y-6"> <PublicSurface className="p-6 md:p-8">
<div className="space-y-6"> <p className="text-xs font-semibold uppercase tracking-[0.18em] text-primary/80">Best Next Step</p>
<h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance">
Use the dedicated contact page for service details.
</h2>
<p className="mt-3 text-base leading-relaxed text-muted-foreground">
That page keeps the full Rocky intake for repairs, moving, manuals, machine sales, and anything that
needs more context. It stays off the rest of the site so these pages can stay lighter and more consistent.
</p>
<div className="mt-6 flex flex-col gap-3 sm:flex-row">
<Link
href="/contact-us#contact-form"
className="inline-flex min-h-11 items-center justify-center rounded-full bg-primary px-5 text-sm font-medium text-primary-foreground transition hover:bg-primary/90"
>
Open Contact Form
</Link>
<a
href={businessConfig.publicCallUrl}
className="inline-flex min-h-11 items-center justify-center rounded-full border border-border bg-white px-5 text-sm font-medium text-foreground transition hover:border-primary/40 hover:text-primary"
>
Call Instead
</a>
</div>
</PublicSurface>
<div className="space-y-5">
<PublicInset className="flex items-start gap-4 p-5"> <PublicInset className="flex items-start gap-4 p-5">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-primary/10 text-primary flex-shrink-0"> <div className="flex h-12 w-12 items-center justify-center rounded-full bg-primary/10 text-primary shrink-0">
<Phone className="h-6 w-6" /> <Phone className="h-6 w-6" />
</div> </div>
<div> <div>
<div className="font-semibold mb-1 text-foreground">Call or Text Us</div> <div className="font-semibold text-foreground">Call or Text Us</div>
<a href={businessConfig.publicCallUrl} className="text-muted-foreground hover:text-foreground transition-colors"> <a href={businessConfig.publicCallUrl} className="mt-1 block text-muted-foreground transition hover:text-foreground">
{businessConfig.publicCallNumber} {businessConfig.publicCallNumber}
</a> </a>
<p className="text-xs text-muted-foreground mt-1">Mon-Fri: 8:00 AM - 5:00 PM</p> <p className="mt-1 text-xs text-muted-foreground">Mon-Fri: 8:00 AM - 5:00 PM</p>
<a href={businessConfig.publicSmsUrl} className="text-xs hover:underline mt-2 block">
Or send a text message
</a>
</div> </div>
</PublicInset> </PublicInset>
<PublicInset className="flex items-start gap-4 p-5"> <PublicInset className="flex items-start gap-4 p-5">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-primary/10 text-primary flex-shrink-0"> <div className="flex h-12 w-12 items-center justify-center rounded-full bg-primary/10 text-primary shrink-0">
<Wrench className="h-6 w-6" />
</div>
<div>
<div className="font-semibold text-foreground">For Repairs or Moving</div>
<p className="mt-1 text-sm leading-relaxed text-muted-foreground">
Include the machine model and a clear text description. You can also text photos or videos to{" "}
<a href={businessConfig.publicSmsUrl} className="font-medium text-foreground underline decoration-primary/35 underline-offset-4 hover:decoration-primary">
{businessConfig.publicSmsNumber}
</a>
.
</p>
</div>
</PublicInset>
<PublicInset className="flex items-start gap-4 p-5">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-primary/10 text-primary shrink-0">
<MapPin className="h-6 w-6" /> <MapPin className="h-6 w-6" />
</div> </div>
<div> <div>
<div className="font-semibold mb-1 text-foreground">Service Areas</div> <div className="font-semibold text-foreground">Service Areas</div>
<p className="text-muted-foreground">Davis, Salt Lake & Utah Counties</p> <p className="mt-1 text-sm leading-relaxed text-muted-foreground">Davis, Salt Lake, and Utah Counties, plus the surrounding Rocky Mountain Vending service footprint already listed on the site.</p>
<p className="text-xs text-muted-foreground mt-1">Serving 20+ cities across Utah</p>
</div> </div>
</PublicInset> </PublicInset>
</div> </div>
</PublicSurface>
<PublicSurface className="p-5 md:p-7">
<div className="mb-4">
<h3 className="text-xl font-semibold mb-2">Contact Form</h3>
<p className="text-sm text-muted-foreground">
Tell us more about your needs and we'll get back to you within 24 hours.
</p>
</div>
<div className="min-h-[738px]">
<ContactForm onSubmit={(data) => console.log('Contact form submitted:', data)} />
</div>
</PublicSurface>
</div> </div>
</div> </div>
</section> </section>

View file

@ -174,7 +174,7 @@ export function ContactForm({ onSubmit, className, defaultIntent = "" }: Contact
<input id="confirm-email" type="email" {...register("confirmEmail")} className="sr-only" tabIndex={-1} /> <input id="confirm-email" type="email" {...register("confirmEmail")} className="sr-only" tabIndex={-1} />
</div> </div>
<div className="rounded-[1.75rem] border border-border/70 bg-background p-5 shadow-sm md:p-6"> <div className="rounded-[1.75rem] border border-border/70 bg-white p-5 shadow-sm md:p-6">
<PublicSectionHeader <PublicSectionHeader
eyebrow="Step 1" eyebrow="Step 1"
title="How should we reach you?" title="How should we reach you?"
@ -233,7 +233,7 @@ export function ContactForm({ onSubmit, className, defaultIntent = "" }: Contact
</div> </div>
</div> </div>
<div className="rounded-[1.75rem] border border-border/70 bg-background p-5 shadow-sm md:p-6"> <div className="rounded-[1.75rem] border border-border/70 bg-white p-5 shadow-sm md:p-6">
<PublicSectionHeader <PublicSectionHeader
eyebrow="Step 2" eyebrow="Step 2"
title="What do you need help with?" title="What do you need help with?"
@ -261,7 +261,7 @@ export function ContactForm({ onSubmit, className, defaultIntent = "" }: Contact
/> />
</div> </div>
<div className="mt-5 grid gap-3 rounded-2xl border border-border/60 bg-background p-4 text-sm text-muted-foreground md:grid-cols-[auto_1fr]"> <div className="mt-5 grid gap-3 rounded-2xl border border-border/60 bg-white p-4 text-sm text-muted-foreground md:grid-cols-[auto_1fr]">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-primary/10 text-primary"> <div className="flex h-10 w-10 items-center justify-center rounded-full bg-primary/10 text-primary">
<Wrench className="h-4 w-4" /> <Wrench className="h-4 w-4" />
</div> </div>
@ -278,7 +278,7 @@ export function ContactForm({ onSubmit, className, defaultIntent = "" }: Contact
</div> </div>
</div> </div>
<div className="rounded-[1.75rem] border border-border/70 bg-background p-5 shadow-sm md:p-6"> <div className="rounded-[1.75rem] border border-border/70 bg-white p-5 shadow-sm md:p-6">
<PublicSectionHeader <PublicSectionHeader
eyebrow="Step 3" eyebrow="Step 3"
title="Tell us the details" title="Tell us the details"
@ -302,26 +302,23 @@ export function ContactForm({ onSubmit, className, defaultIntent = "" }: Contact
</div> </div>
</div> </div>
<div className="rounded-[1.75rem] border border-border/70 bg-background p-5 shadow-sm md:p-6"> <div className="rounded-[1.75rem] border border-border/70 bg-white p-5 shadow-sm md:p-6">
<PublicSectionHeader <PublicSectionHeader
eyebrow="Step 4" eyebrow="Step 4"
title="Text updates" title="Text updates"
description="Required service consent covers scheduling, support, and follow-up texts for this request. Marketing texts stay separate and optional." description="This single opt-in covers scheduling, support, and follow-up texts for this request."
/> />
<div className="mt-5"> <div className="mt-5">
<SmsConsentFields <SmsConsentFields
idPrefix="contact" idPrefix="contact"
serviceChecked={Boolean(watchedValues.serviceTextConsent)} serviceChecked={Boolean(watchedValues.serviceTextConsent)}
marketingChecked={Boolean(watchedValues.marketingTextConsent)}
onServiceChange={(checked) => setValue("serviceTextConsent", checked, { shouldValidate: true })} onServiceChange={(checked) => setValue("serviceTextConsent", checked, { shouldValidate: true })}
onMarketingChange={(checked) => setValue("marketingTextConsent", checked, { shouldValidate: true })}
serviceError={errors.serviceTextConsent?.message} serviceError={errors.serviceTextConsent?.message}
marketingError={errors.marketingTextConsent?.message}
/> />
</div> </div>
</div> </div>
<div className="rounded-[1.5rem] border border-border/60 bg-background px-4 py-4 text-sm text-muted-foreground"> <div className="rounded-[1.5rem] border border-border/60 bg-white px-4 py-4 text-sm text-muted-foreground">
<div className="flex flex-col gap-3 md:flex-row md:items-start md:justify-between"> <div className="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
<div className="space-y-1"> <div className="space-y-1">
<p className="font-medium text-foreground">Need a faster handoff?</p> <p className="font-medium text-foreground">Need a faster handoff?</p>
@ -330,14 +327,14 @@ export function ContactForm({ onSubmit, className, defaultIntent = "" }: Contact
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
<a <a
href={businessConfig.publicCallUrl} href={businessConfig.publicCallUrl}
className="inline-flex items-center gap-2 rounded-full border border-border bg-background px-4 py-2 font-medium text-foreground transition hover:border-primary/40 hover:text-primary" className="inline-flex items-center gap-2 rounded-full border border-border bg-white px-4 py-2 font-medium text-foreground transition hover:border-primary/40 hover:text-primary"
> >
<PhoneCall className="h-4 w-4" /> <PhoneCall className="h-4 w-4" />
Call Call
</a> </a>
<a <a
href={businessConfig.publicSmsUrl} href={businessConfig.publicSmsUrl}
className="inline-flex items-center gap-2 rounded-full border border-border bg-background px-4 py-2 font-medium text-foreground transition hover:border-primary/40 hover:text-primary" className="inline-flex items-center gap-2 rounded-full border border-border bg-white px-4 py-2 font-medium text-foreground transition hover:border-primary/40 hover:text-primary"
> >
<MessageSquare className="h-4 w-4" /> <MessageSquare className="h-4 w-4" />
Text Photos Text Photos

View file

@ -16,7 +16,7 @@ const buttonVariants = cva(
destructive: destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline: outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", "border bg-white shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary: secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80", "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: ghost:

View file

@ -27,7 +27,7 @@ const FormInput = React.forwardRef<HTMLInputElement, FormInputProps>(
type={type} type={type}
data-slot="input" data-slot="input"
className={cn( className={cn(
"h-12 w-full min-w-0 rounded-xl border border-border/70 bg-background/85 px-4 text-base text-foreground shadow-sm transition outline-none", "h-12 w-full min-w-0 rounded-xl border border-border/70 bg-white px-4 text-base text-foreground shadow-sm transition outline-none",
"placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground", "placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground",
"focus:border-primary focus:ring-4 focus:ring-primary/15 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus:border-primary focus:ring-4 focus:ring-primary/15 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
error ? "border-destructive focus:ring-destructive/10" : "", error ? "border-destructive focus:ring-destructive/10" : "",

View file

@ -34,7 +34,7 @@ const FormSelect = React.forwardRef<HTMLSelectElement, FormSelectProps>(
id={selectId} id={selectId}
ref={ref} ref={ref}
className={cn( className={cn(
"h-12 w-full appearance-none rounded-xl border border-border/70 bg-background/85 px-4 pr-11 text-base text-foreground shadow-sm transition outline-none", "h-12 w-full appearance-none rounded-xl border border-border/70 bg-white px-4 pr-11 text-base text-foreground shadow-sm transition outline-none",
"focus:border-primary focus:ring-4 focus:ring-primary/15 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus:border-primary focus:ring-4 focus:ring-primary/15 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
error ? "border-destructive focus:ring-destructive/10" : "", error ? "border-destructive focus:ring-destructive/10" : "",
className, className,

View file

@ -26,7 +26,7 @@ const FormTextarea = React.forwardRef<HTMLTextAreaElement, FormTextareaProps>(
id={textareaId} id={textareaId}
data-slot="textarea" data-slot="textarea"
className={cn( className={cn(
"min-h-[136px] w-full rounded-xl border border-border/70 bg-background/85 px-4 py-3 text-base text-foreground shadow-sm transition outline-none", "min-h-[136px] w-full rounded-xl border border-border/70 bg-white px-4 py-3 text-base text-foreground shadow-sm transition outline-none",
"placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground", "placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground",
"focus:border-primary focus:ring-4 focus:ring-primary/15 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus:border-primary focus:ring-4 focus:ring-primary/15 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
error ? "border-destructive focus:ring-destructive/10" : "", error ? "border-destructive focus:ring-destructive/10" : "",

View file

@ -191,7 +191,7 @@ export function RequestMachineForm({ onSubmit, className }: RequestMachineFormPr
<input id="confirm-email" type="email" {...register("confirmEmail")} className="sr-only" tabIndex={-1} /> <input id="confirm-email" type="email" {...register("confirmEmail")} className="sr-only" tabIndex={-1} />
</div> </div>
<div className="rounded-[1.75rem] border border-border/70 bg-background p-5 shadow-sm md:p-6"> <div className="rounded-[1.75rem] border border-border/70 bg-white p-5 shadow-sm md:p-6">
<PublicSectionHeader <PublicSectionHeader
eyebrow="Step 1" eyebrow="Step 1"
title="Business contact" title="Business contact"
@ -274,21 +274,21 @@ export function RequestMachineForm({ onSubmit, className }: RequestMachineFormPr
</div> </div>
</div> </div>
<div className="rounded-[1.75rem] border border-border/70 bg-background p-5 shadow-sm md:p-6"> <div className="rounded-[1.75rem] border border-border/70 bg-white p-5 shadow-sm md:p-6">
<PublicSectionHeader <PublicSectionHeader
eyebrow="Step 2" eyebrow="Step 2"
title="What should we plan for?" title="What should we plan for?"
description="Tell us what kind of setup you'd like so we can recommend the right mix." description="Tell us what kind of setup you'd like so we can recommend the right mix."
/> />
<div className="mt-5 grid gap-3 rounded-[1.5rem] border border-border/60 bg-background p-4 md:grid-cols-2"> <div className="mt-5 grid gap-3 rounded-[1.5rem] border border-border/60 bg-white p-4 md:grid-cols-2">
{MACHINE_TYPE_OPTIONS.map((option) => { {MACHINE_TYPE_OPTIONS.map((option) => {
const isChecked = selectedMachineTypes.includes(option.value) const isChecked = selectedMachineTypes.includes(option.value)
return ( return (
<label <label
key={option.value} key={option.value}
htmlFor={`machine-type-${option.value}`} htmlFor={`machine-type-${option.value}`}
className="flex cursor-pointer items-start gap-3 rounded-xl border border-border/60 bg-background px-4 py-3 transition hover:border-primary/35" className="flex cursor-pointer items-start gap-3 rounded-xl border border-border/60 bg-white px-4 py-3 transition hover:border-primary/35"
> >
<Checkbox <Checkbox
id={`machine-type-${option.value}`} id={`machine-type-${option.value}`}
@ -320,7 +320,7 @@ export function RequestMachineForm({ onSubmit, className }: RequestMachineFormPr
}, },
})} })}
/> />
<div className="rounded-2xl border border-border/60 bg-background p-4 text-sm text-muted-foreground"> <div className="rounded-2xl border border-border/60 bg-white p-4 text-sm text-muted-foreground">
<div className="flex items-center gap-2 font-medium text-foreground"> <div className="flex items-center gap-2 font-medium text-foreground">
<ClipboardCheck className="h-4 w-4 text-primary" /> <ClipboardCheck className="h-4 w-4 text-primary" />
Free placement intake Free placement intake
@ -332,7 +332,7 @@ export function RequestMachineForm({ onSubmit, className }: RequestMachineFormPr
</div> </div>
</div> </div>
<div className="rounded-[1.75rem] border border-border/70 bg-background p-5 shadow-sm md:p-6"> <div className="rounded-[1.75rem] border border-border/70 bg-white p-5 shadow-sm md:p-6">
<PublicSectionHeader <PublicSectionHeader
eyebrow="Step 3" eyebrow="Step 3"
title="Anything else we should know?" title="Anything else we should know?"
@ -349,21 +349,18 @@ export function RequestMachineForm({ onSubmit, className }: RequestMachineFormPr
</div> </div>
</div> </div>
<div className="rounded-[1.75rem] border border-border/70 bg-background p-5 shadow-sm md:p-6"> <div className="rounded-[1.75rem] border border-border/70 bg-white p-5 shadow-sm md:p-6">
<PublicSectionHeader <PublicSectionHeader
eyebrow="Step 4" eyebrow="Step 4"
title="Text updates" title="Text updates"
description="Required service consent covers scheduling, installation planning, service, and follow-up texts for this request. Marketing texts stay separate and optional." description="This single opt-in covers scheduling, installation planning, service, and follow-up texts for this request."
/> />
<div className="mt-5"> <div className="mt-5">
<SmsConsentFields <SmsConsentFields
idPrefix="request-machine" idPrefix="request-machine"
serviceChecked={Boolean(watchedValues.serviceTextConsent)} serviceChecked={Boolean(watchedValues.serviceTextConsent)}
marketingChecked={Boolean(watchedValues.marketingTextConsent)}
onServiceChange={(checked) => setValue("serviceTextConsent", checked, { shouldValidate: true })} onServiceChange={(checked) => setValue("serviceTextConsent", checked, { shouldValidate: true })}
onMarketingChange={(checked) => setValue("marketingTextConsent", checked, { shouldValidate: true })}
serviceError={errors.serviceTextConsent?.message} serviceError={errors.serviceTextConsent?.message}
marketingError={errors.marketingTextConsent?.message}
/> />
</div> </div>
</div> </div>
@ -383,7 +380,7 @@ export function RequestMachineForm({ onSubmit, className }: RequestMachineFormPr
</FormButton> </FormButton>
<a <a
href={businessConfig.publicCallUrl} href={businessConfig.publicCallUrl}
className="inline-flex min-h-11 items-center justify-center gap-2 rounded-full border border-border bg-background px-4 text-sm font-medium text-foreground transition hover:border-primary/40 hover:text-primary" className="inline-flex min-h-11 items-center justify-center gap-2 rounded-full border border-border bg-white px-4 text-sm font-medium text-foreground transition hover:border-primary/40 hover:text-primary"
> >
<PhoneCall className="h-4 w-4" /> <PhoneCall className="h-4 w-4" />
Call Call

View file

@ -32,10 +32,10 @@ function PolicyLinks() {
type SmsConsentFieldsProps = { type SmsConsentFieldsProps = {
idPrefix: string idPrefix: string
marketingChecked: boolean marketingChecked?: boolean
marketingError?: string marketingError?: string
mode?: "chat" | "forms" mode?: "chat" | "forms"
onMarketingChange: (checked: boolean) => void onMarketingChange?: (checked: boolean) => void
onServiceChange: (checked: boolean) => void onServiceChange: (checked: boolean) => void
serviceChecked: boolean serviceChecked: boolean
serviceError?: string serviceError?: string
@ -43,17 +43,17 @@ type SmsConsentFieldsProps = {
export function SmsConsentFields({ export function SmsConsentFields({
idPrefix, idPrefix,
marketingChecked, marketingChecked: _marketingChecked,
marketingError, marketingError: _marketingError,
mode = "forms", mode = "forms",
onMarketingChange, onMarketingChange: _onMarketingChange,
onServiceChange, onServiceChange,
serviceChecked, serviceChecked,
serviceError, serviceError,
}: SmsConsentFieldsProps) { }: SmsConsentFieldsProps) {
return ( return (
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-start gap-3 rounded-2xl border border-border/60 bg-background/90 px-4 py-3"> <div className="flex items-start gap-3 rounded-2xl border border-border/60 bg-white px-4 py-3">
<Checkbox <Checkbox
id={`${idPrefix}-service-consent`} id={`${idPrefix}-service-consent`}
checked={serviceChecked} checked={serviceChecked}
@ -64,36 +64,13 @@ export function SmsConsentFields({
htmlFor={`${idPrefix}-service-consent`} htmlFor={`${idPrefix}-service-consent`}
className="text-xs leading-relaxed text-muted-foreground" className="text-xs leading-relaxed text-muted-foreground"
> >
I agree to receive conversational SMS from {businessConfig.legalName} about my inquiry, scheduling, I agree to Terms & Conditions provided by {businessConfig.legalName}. By providing my phone number, I agree
support, repairs, moving, and follow-up. Message frequency varies. Message and data rates may apply. to receive text messages about my inquiry, scheduling, support, repairs, moving, and follow-up. Message
Reply STOP to opt out and HELP for help. Consent is not a condition of purchase. frequency varies. Message and data rates may apply. Reply STOP to opt out and HELP for help.
<PolicyLinks /> <PolicyLinks />
</Label> </Label>
</div> </div>
{serviceError ? <p className="text-xs text-destructive">{serviceError}</p> : null} {serviceError ? <p className="text-xs text-destructive">{serviceError}</p> : null}
{mode === "forms" ? (
<>
<div className="flex items-start gap-3 rounded-2xl border border-border/60 bg-background/90 px-4 py-3">
<Checkbox
id={`${idPrefix}-marketing-consent`}
checked={marketingChecked}
onCheckedChange={(checked) => onMarketingChange(Boolean(checked))}
className="mt-0.5"
/>
<Label
htmlFor={`${idPrefix}-marketing-consent`}
className="text-xs leading-relaxed text-muted-foreground"
>
I agree to receive promotional and marketing SMS from {businessConfig.legalName}. Message frequency
varies. Message and data rates may apply. Reply STOP to opt out and HELP for help. Consent is not a
condition of purchase.
<PolicyLinks />
</Label>
</div>
{marketingError ? <p className="text-xs text-destructive">{marketingError}</p> : null}
</>
) : null}
</div> </div>
) )
} }

View file

@ -0,0 +1,37 @@
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { GetFreeMachineModal } from "@/components/get-free-machine-modal"
import { cn } from "@/lib/utils"
type GetFreeMachineCtaProps = {
buttonLabel?: string
className?: string
size?: "default" | "sm" | "lg"
variant?: "default" | "outline" | "secondary" | "ghost" | "link" | "brand"
}
export function GetFreeMachineCta({
buttonLabel = "Get Free Machine",
className,
size = "lg",
variant = "default",
}: GetFreeMachineCtaProps) {
const [isOpen, setIsOpen] = useState(false)
return (
<>
<Button
type="button"
size={size}
variant={variant}
className={cn("rounded-full px-6", className)}
onClick={() => setIsOpen(true)}
>
{buttonLabel}
</Button>
<GetFreeMachineModal open={isOpen} onOpenChange={setIsOpen} />
</>
)
}

View file

@ -565,7 +565,7 @@ export function ManualsPageClient({
placeholder="Search manuals by name, manufacturer, or category..." placeholder="Search manuals by name, manufacturer, or category..."
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="h-12 rounded-xl border-border/70 bg-background/85 pl-10 shadow-sm" className="h-12 rounded-xl border-border/70 bg-white pl-10 shadow-sm"
/> />
</div> </div>
@ -588,7 +588,7 @@ export function ManualsPageClient({
} }
}} }}
> >
<SelectTrigger className="w-full rounded-xl border-border/70 bg-background/85 shadow-sm sm:w-[200px]"> <SelectTrigger className="w-full rounded-xl border-border/70 bg-white shadow-sm sm:w-[200px]">
<SelectValue placeholder="All Manufacturers" /> <SelectValue placeholder="All Manufacturers" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -614,7 +614,7 @@ export function ManualsPageClient({
}} }}
disabled={!selectedManufacturer && filteredCategories.length === categories.length} disabled={!selectedManufacturer && filteredCategories.length === categories.length}
> >
<SelectTrigger className="w-full rounded-xl border-border/70 bg-background/85 shadow-sm sm:w-[200px]"> <SelectTrigger className="w-full rounded-xl border-border/70 bg-white shadow-sm sm:w-[200px]">
<SelectValue placeholder="All Categories" /> <SelectValue placeholder="All Categories" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -665,7 +665,7 @@ export function ManualsPageClient({
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 sm:gap-4"> <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 sm:gap-4">
<div className="flex items-center gap-3 w-full sm:w-auto"> <div className="flex items-center gap-3 w-full sm:w-auto">
<span className="text-sm text-muted-foreground flex-shrink-0">View:</span> <span className="text-sm text-muted-foreground flex-shrink-0">View:</span>
<div className="inline-flex flex-1 items-center rounded-full border border-border/70 bg-background/90 p-1 shadow-sm sm:flex-initial"> <div className="inline-flex flex-1 items-center rounded-full border border-border/70 bg-white p-1 shadow-sm sm:flex-initial">
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"

View file

@ -54,7 +54,7 @@ export function PublicSurface({
return ( return (
<Component <Component
className={cn( className={cn(
"rounded-[2rem] border border-border/70 bg-background p-5 shadow-[0_24px_60px_rgba(15,23,42,0.08)] md:p-7", "rounded-[2rem] border border-border/70 bg-white p-5 shadow-[0_20px_48px_rgba(15,23,42,0.08)] md:p-7",
className, className,
)} )}
{...props} {...props}
@ -72,7 +72,7 @@ export function PublicInset({
return ( return (
<div <div
className={cn( className={cn(
"rounded-[1.5rem] border border-border/60 bg-background p-4 shadow-sm", "rounded-[1.5rem] border border-border/60 bg-white p-4 shadow-sm",
className, className,
)} )}
{...props} {...props}

View file

@ -11,7 +11,7 @@ export function RequestMachineSection() {
<section id="request-machine" className="bg-background py-16 md:py-24"> <section id="request-machine" className="bg-background py-16 md:py-24">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<div className="grid gap-8 lg:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] lg:items-start"> <div className="grid gap-8 lg:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] lg:items-start">
<PublicSurface className="bg-background p-6 md:p-8 lg:sticky lg:top-28"> <PublicSurface className="bg-white p-6 md:p-8 lg:sticky lg:top-28">
<div className="inline-flex items-center gap-2 rounded-full bg-primary/10 px-3 py-1 text-xs font-semibold uppercase tracking-[0.16em] text-primary"> <div className="inline-flex items-center gap-2 rounded-full bg-primary/10 px-3 py-1 text-xs font-semibold uppercase tracking-[0.16em] text-primary">
<Package className="h-4 w-4" /> <Package className="h-4 w-4" />
Free Placement Free Placement
@ -33,7 +33,7 @@ export function RequestMachineSection() {
</PublicInset> </PublicInset>
<div className="mt-6 flex flex-wrap gap-3"> <div className="mt-6 flex flex-wrap gap-3">
<a href={businessConfig.publicCallUrl} className="inline-flex min-h-11 items-center justify-center rounded-full border border-border bg-background px-4 text-sm font-medium text-foreground transition hover:border-primary/40 hover:text-primary"> <a href={businessConfig.publicCallUrl} 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">
Call Instead Call Instead
</a> </a>
<Link href="/contact-us#contact-form" className="inline-flex min-h-11 items-center gap-2 rounded-full text-sm font-medium text-foreground transition hover:text-primary"> <Link href="/contact-us#contact-form" className="inline-flex min-h-11 items-center gap-2 rounded-full text-sm font-medium text-foreground transition hover:text-primary">
@ -43,7 +43,7 @@ export function RequestMachineSection() {
</div> </div>
</PublicSurface> </PublicSurface>
<PublicSurface className="bg-background p-5 md:p-7"> <PublicSurface className="bg-white p-5 md:p-7">
<RequestMachineForm onSubmit={(data) => console.log("Machine request form submitted:", data)} /> <RequestMachineForm onSubmit={(data) => console.log("Machine request form submitted:", data)} />
</PublicSurface> </PublicSurface>
</div> </div>

View file

@ -141,11 +141,11 @@ export function ReviewsPage() {
</p> </p>
</div> </div>
<div className="mt-6 grid gap-4 sm:grid-cols-2"> <div className="mt-6 grid gap-4 sm:grid-cols-2">
<Link href="/#request-machine" className="rounded-[1.5rem] border border-border/60 bg-background/90 p-5 text-left transition hover:border-primary/30 hover:text-primary"> <Link href="/#request-machine" className="rounded-[1.5rem] border border-border/60 bg-white p-5 text-left transition hover:border-primary/30 hover:text-primary">
<h3 className="text-lg font-semibold text-foreground">Free Placement</h3> <h3 className="text-lg font-semibold text-foreground">Free Placement</h3>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">Start a request for free vending machine placement at your business.</p> <p className="mt-2 text-sm leading-relaxed text-muted-foreground">Start a request for free vending machine placement at your business.</p>
</Link> </Link>
<Link href="/contact-us#contact-form" className="rounded-[1.5rem] border border-border/60 bg-background/90 p-5 text-left transition hover:border-primary/30 hover:text-primary"> <Link href="/contact-us#contact-form" className="rounded-[1.5rem] border border-border/60 bg-white p-5 text-left transition hover:border-primary/30 hover:text-primary">
<h3 className="text-lg font-semibold text-foreground">Service or Sales</h3> <h3 className="text-lg font-semibold text-foreground">Service or Sales</h3>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">Reach out about repairs, moving, manuals, parts, or machine sales.</p> <p className="mt-2 text-sm leading-relaxed text-muted-foreground">Reach out about repairs, moving, manuals, parts, or machine sales.</p>
</Link> </Link>

View file

@ -398,7 +398,7 @@ export function SiteChatWidget() {
{isOpen ? ( {isOpen ? (
<div <div
data-testid="site-chat-panel" data-testid="site-chat-panel"
className="pointer-events-auto flex w-[min(24rem,calc(100vw-1.5rem))] flex-col overflow-hidden rounded-[1.75rem] border border-border/70 bg-background/95 shadow-[0_24px_80px_rgba(0,0,0,0.2)] backdrop-blur-xl" className="pointer-events-auto flex w-[min(24rem,calc(100vw-1.5rem))] flex-col overflow-hidden rounded-[1.75rem] border border-border/70 bg-white shadow-[0_24px_80px_rgba(0,0,0,0.2)]"
style={{ maxHeight: PANEL_MAX_HEIGHT }} style={{ maxHeight: PANEL_MAX_HEIGHT }}
> >
<div className="flex items-start justify-between border-b border-border/70 px-4 py-4"> <div className="flex items-start justify-between border-b border-border/70 px-4 py-4">
@ -412,7 +412,7 @@ export function SiteChatWidget() {
<button <button
type="button" type="button"
onClick={() => setIsOpen(false)} onClick={() => setIsOpen(false)}
className="inline-flex h-10 w-10 items-center justify-center rounded-full border border-border bg-background text-foreground transition hover:border-primary/50 hover:text-primary focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-primary/20" className="inline-flex h-10 w-10 items-center justify-center rounded-full border border-border bg-white text-foreground transition hover:border-primary/50 hover:text-primary focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-primary/20"
aria-label="Close chat" aria-label="Close chat"
> >
<X className="h-4 w-4" /> <X className="h-4 w-4" />
@ -422,7 +422,7 @@ export function SiteChatWidget() {
{!profile ? ( {!profile ? (
<div className="min-h-0 overflow-y-auto px-4 py-4"> <div className="min-h-0 overflow-y-auto px-4 py-4">
<form onSubmit={handleProfileSubmit} className="space-y-4"> <form onSubmit={handleProfileSubmit} className="space-y-4">
<div className="rounded-[1.5rem] border border-border/70 bg-background/80 p-4 shadow-sm"> <div className="rounded-[1.5rem] border border-border/70 bg-white p-4 shadow-sm">
<p className="text-sm font-medium text-foreground">Start with your details</p> <p className="text-sm font-medium text-foreground">Start with your details</p>
<p className="mt-1 text-xs leading-relaxed text-muted-foreground"> <p className="mt-1 text-xs leading-relaxed text-muted-foreground">
We use this to route the conversation to the right team member. We use this to route the conversation to the right team member.
@ -469,7 +469,7 @@ export function SiteChatWidget() {
</div> </div>
</div> </div>
<div className="rounded-[1.5rem] border border-border/70 bg-background/80 p-4 shadow-sm"> <div className="rounded-[1.5rem] border border-border/70 bg-white p-4 shadow-sm">
<p className="text-sm font-medium text-foreground">Text updates</p> <p className="text-sm font-medium text-foreground">Text updates</p>
<p className="mt-1 text-xs leading-relaxed text-muted-foreground"> <p className="mt-1 text-xs leading-relaxed text-muted-foreground">
Required service consent covers scheduling, support, repairs, moving, and follow-up texts for this request. Required service consent covers scheduling, support, repairs, moving, and follow-up texts for this request.
@ -479,7 +479,6 @@ export function SiteChatWidget() {
idPrefix="site-chat" idPrefix="site-chat"
mode="chat" mode="chat"
serviceChecked={profileDraft.serviceTextConsent} serviceChecked={profileDraft.serviceTextConsent}
marketingChecked={profileDraft.marketingTextConsent}
onServiceChange={(checked) => onServiceChange={(checked) =>
setProfileDraft((current) => ({ setProfileDraft((current) => ({
...current, ...current,
@ -488,7 +487,6 @@ export function SiteChatWidget() {
consentSourcePage: pathname || "/", consentSourcePage: pathname || "/",
})) }))
} }
onMarketingChange={() => undefined}
serviceError={profileError && !profileDraft.serviceTextConsent ? profileError : undefined} serviceError={profileError && !profileDraft.serviceTextConsent ? profileError : undefined}
/> />
</div> </div>
@ -506,7 +504,7 @@ export function SiteChatWidget() {
<a <a
href={bootstrap.callUrl} href={bootstrap.callUrl}
className="inline-flex min-h-11 items-center justify-center gap-2 rounded-full border border-border bg-background px-4 text-sm font-medium text-foreground transition hover:border-primary/50 hover:text-primary" className="inline-flex min-h-11 items-center justify-center gap-2 rounded-full border border-border bg-white px-4 text-sm font-medium text-foreground transition hover:border-primary/50 hover:text-primary"
> >
<Phone className="h-4 w-4" /> <Phone className="h-4 w-4" />
Call Call
@ -571,7 +569,7 @@ export function SiteChatWidget() {
</div> </div>
</div> </div>
<div className="border-t border-border/70 bg-background/95 px-4 py-4"> <div className="border-t border-border/70 bg-white px-4 py-4">
<form onSubmit={handleSubmit} className="space-y-3"> <form onSubmit={handleSubmit} className="space-y-3">
<label htmlFor="site-chat-input" className="text-sm font-semibold text-foreground"> <label htmlFor="site-chat-input" className="text-sm font-semibold text-foreground">
Message Message
@ -585,7 +583,7 @@ export function SiteChatWidget() {
placeholder="Describe what you need" placeholder="Describe what you need"
rows={3} rows={3}
disabled={isSending} disabled={isSending}
className="min-h-24 w-full rounded-2xl border border-border/70 bg-background px-4 py-3 text-sm text-foreground outline-none transition placeholder:text-muted-foreground focus:border-primary focus:ring-4 focus:ring-primary/15 disabled:cursor-not-allowed disabled:opacity-60" className="min-h-24 w-full rounded-2xl border border-border/70 bg-white px-4 py-3 text-sm text-foreground outline-none transition placeholder:text-muted-foreground focus:border-primary focus:ring-4 focus:ring-primary/15 disabled:cursor-not-allowed disabled:opacity-60"
/> />
<SupportHint <SupportHint
@ -609,7 +607,7 @@ export function SiteChatWidget() {
<a <a
href={bootstrap.callUrl} href={bootstrap.callUrl}
className="inline-flex min-h-11 items-center justify-center gap-2 rounded-full border border-border bg-background px-4 text-sm font-medium text-foreground transition hover:border-primary/50 hover:text-primary" className="inline-flex min-h-11 items-center justify-center gap-2 rounded-full border border-border bg-white px-4 text-sm font-medium text-foreground transition hover:border-primary/50 hover:text-primary"
> >
<Phone className="h-4 w-4" /> <Phone className="h-4 w-4" />
Call Call
@ -636,7 +634,7 @@ export function SiteChatWidget() {
type="button" type="button"
data-testid="site-chat-launcher" data-testid="site-chat-launcher"
onClick={() => setIsOpen(true)} onClick={() => setIsOpen(true)}
className="pointer-events-auto inline-flex h-14 w-14 items-center justify-center rounded-full border border-white/70 bg-background/95 shadow-[0_20px_60px_rgba(0,0,0,0.18)] transition hover:-translate-y-0.5 hover:shadow-[0_24px_68px_rgba(0,0,0,0.22)] focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-primary/20" className="pointer-events-auto inline-flex h-14 w-14 items-center justify-center rounded-full border border-white/70 bg-white shadow-[0_20px_60px_rgba(0,0,0,0.18)] transition hover:-translate-y-0.5 hover:shadow-[0_24px_68px_rgba(0,0,0,0.22)] focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-primary/20"
aria-label={`Open chat with ${bootstrap.assistantName}`} aria-label={`Open chat with ${bootstrap.assistantName}`}
> >
<AssistantAvatar src={bootstrap.avatarSrc} alt={bootstrap.assistantName} sizeClassName="h-12 w-12" /> <AssistantAvatar src={bootstrap.avatarSrc} alt={bootstrap.assistantName} sizeClassName="h-12 w-12" />

View file

@ -13,7 +13,7 @@ const buttonVariants = cva(
destructive: destructive:
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
outline: outline:
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50', 'border bg-white shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
secondary: secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80', 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: ghost:

View file

@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<'div'>) {
<div <div
data-slot="card" data-slot="card"
className={cn( className={cn(
'bg-card text-card-foreground flex flex-col gap-6 rounded-[1.75rem] border border-border/70 py-6 shadow-[0_18px_45px_rgba(15,23,42,0.08)]', 'bg-white text-card-foreground flex flex-col gap-6 rounded-[1.75rem] border border-border/70 py-6 shadow-[0_18px_45px_rgba(15,23,42,0.08)]',
className, className,
)} )}
{...props} {...props}

View file

@ -5,6 +5,7 @@ import { VendingMachinesShowcase } from "@/components/vending-machines-showcase"
import { CheckCircle2, CreditCard, MonitorSmartphone, Package, Wrench } from "lucide-react" import { CheckCircle2, CreditCard, MonitorSmartphone, Package, Wrench } from "lucide-react"
import Link from "next/link" import Link from "next/link"
import { PublicInset, PublicPageHeader, PublicSurface } from "@/components/public-surface" import { PublicInset, PublicPageHeader, PublicSurface } from "@/components/public-surface"
import { GetFreeMachineCta } from "@/components/get-free-machine-cta"
export function VendingMachinesPage() { export function VendingMachinesPage() {
return ( return (
@ -30,7 +31,7 @@ export function VendingMachinesPage() {
["Cashless payments", "Integrated card readers and secure payment options for modern locations."], ["Cashless payments", "Integrated card readers and secure payment options for modern locations."],
["Full service and support", "Maintenance, restocking, and local follow-up are part of the experience."], ["Full service and support", "Maintenance, restocking, and local follow-up are part of the experience."],
].map(([title, body]) => ( ].map(([title, body]) => (
<div key={title} className="flex items-start gap-4 rounded-[1.5rem] border border-border/60 bg-background/85 p-5 shadow-sm"> <div key={title} className="flex items-start gap-4 rounded-[1.5rem] border border-border/60 bg-white p-5 shadow-sm">
<CheckCircle2 className="mt-0.5 h-6 w-6 shrink-0 text-primary" /> <CheckCircle2 className="mt-0.5 h-6 w-6 shrink-0 text-primary" />
<div> <div>
<h3 className="text-lg font-semibold text-foreground">{title}</h3> <h3 className="text-lg font-semibold text-foreground">{title}</h3>
@ -51,9 +52,7 @@ export function VendingMachinesPage() {
<Button asChild size="lg" className="h-11 rounded-full px-6"> <Button asChild size="lg" className="h-11 rounded-full px-6">
<Link href="/contact-us#contact-form">Contact Us</Link> <Link href="/contact-us#contact-form">Contact Us</Link>
</Button> </Button>
<Button asChild size="lg" variant="outline" className="h-11 rounded-full px-6"> <GetFreeMachineCta buttonLabel="Get Free Machine" className="h-11 px-6" variant="outline" />
<Link href="/#request-machine">Get Free Machine</Link>
</Button>
</div> </div>
</PublicSurface> </PublicSurface>
</section> </section>

View file

@ -3,6 +3,7 @@
import { ReactNode } from "react" import { ReactNode } from "react"
import { Card, CardContent } from "@/components/ui/card" import { Card, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { GetFreeMachineCta } from "@/components/get-free-machine-cta"
import Link from "next/link" import Link from "next/link"
import { CheckCircle2 } from "lucide-react" import { CheckCircle2 } from "lucide-react"
@ -127,9 +128,11 @@ export function WhoWeServePage({ title, content }: WhoWeServePageProps) {
<Button asChild size="lg" className="bg-white text-[var(--primary)] hover:bg-white/90 text-lg h-12 px-8 font-semibold"> <Button asChild size="lg" className="bg-white text-[var(--primary)] hover:bg-white/90 text-lg h-12 px-8 font-semibold">
<Link href="/contact-us">Contact Us</Link> <Link href="/contact-us">Contact Us</Link>
</Button> </Button>
<Button asChild size="lg" variant="outline" className="bg-transparent border-2 border-white text-white hover:bg-white/10 text-lg h-12 px-8 font-semibold"> <GetFreeMachineCta
<Link href="/#request-machine">Get Free Machine</Link> buttonLabel="Get Free Machine"
</Button> className="h-12 border-2 border-white bg-transparent px-8 text-lg font-semibold text-white hover:bg-white/10"
variant="outline"
/>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
@ -137,4 +140,3 @@ export function WhoWeServePage({ title, content }: WhoWeServePageProps) {
</div> </div>
) )
} }