deploy: reset public forms and white surfaces
This commit is contained in:
parent
e2124cfa66
commit
7160259afd
25 changed files with 239 additions and 191 deletions
|
|
@ -18,7 +18,7 @@ import { ContactPage } from '@/components/contact-page';
|
|||
import { AboutPage } from '@/components/about-page';
|
||||
import { WhoWeServePage } from '@/components/who-we-serve-page';
|
||||
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
|
||||
export const dynamic = 'force-static';
|
||||
|
|
@ -346,17 +346,23 @@ function renderLocationPage(locationData: any, locationSlug: string) {
|
|||
</Card>
|
||||
</div>
|
||||
|
||||
{/* CRM Form */}
|
||||
{/* Placement CTA */}
|
||||
<PublicSurface>
|
||||
<CardContent className="p-1">
|
||||
<div className="text-center mb-6">
|
||||
<h3 className="text-2xl font-bold mb-2">Get Your Free Vending Machine</h3>
|
||||
<p className="text-muted-foreground">
|
||||
Tell us about your location and we'll follow up within one business day.
|
||||
</p>
|
||||
<div className="p-1 text-center">
|
||||
<h3 className="text-2xl font-bold mb-2">Get Your Free Vending Machine</h3>
|
||||
<p className="text-muted-foreground">
|
||||
Open the Rocky placement popup and we'll follow up within one business day with the best next step for your location.
|
||||
</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>
|
||||
<RequestMachineForm />
|
||||
</CardContent>
|
||||
</div>
|
||||
</PublicSurface>
|
||||
</section>
|
||||
|
||||
|
|
@ -404,9 +410,7 @@ function renderLocationPage(locationData: any, locationSlug: string) {
|
|||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<Button asChild size="lg" className="rounded-full bg-primary px-6 hover:bg-primary/90">
|
||||
<Link href="#contact">Get Your Free Machine Today</Link>
|
||||
</Button>
|
||||
<GetFreeMachineCta buttonLabel="Get Your Free Machine Today" />
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { businessConfig } from "@/lib/seo-config"
|
|||
import { MapPin, Phone, ArrowRight, Wrench, Clock } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { PublicInset, PublicPageHeader, PublicSurface } from "@/components/public-surface"
|
||||
import { GetFreeMachineCta } from "@/components/get-free-machine-cta"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Service Areas | Vending Machines Across Utah | Rocky Mountain Vending",
|
||||
|
|
@ -135,9 +136,7 @@ export default function ServiceAreasPage() {
|
|||
</a>
|
||||
<p className="mt-2 text-sm text-muted-foreground">We'll confirm delivery range, support availability, and the best intake path for your location.</p>
|
||||
</PublicInset>
|
||||
<Button asChild className="h-11 rounded-full px-5">
|
||||
<Link href="/#request-machine">Request a Free Machine</Link>
|
||||
</Button>
|
||||
<GetFreeMachineCta buttonLabel="Request a Free Machine" className="h-11 px-5" />
|
||||
</div>
|
||||
</PublicSurface>
|
||||
</div>
|
||||
|
|
@ -247,9 +246,9 @@ export default function ServiceAreasPage() {
|
|||
</PublicInset>
|
||||
))}
|
||||
</div>
|
||||
<Button asChild className="mt-6 h-11 rounded-full px-5">
|
||||
<Link href="/#request-machine">Get Your Free Machine</Link>
|
||||
</Button>
|
||||
<div className="mt-6">
|
||||
<GetFreeMachineCta buttonLabel="Get Your Free Machine" className="h-11 px-5" />
|
||||
</div>
|
||||
</PublicSurface>
|
||||
|
||||
<PublicSurface>
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ import type { Metadata } from "next";
|
|||
import Image from "next/image";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PageWrapper, PageHeader } from "@/components/page-wrapper";
|
||||
import { businessConfig } from "@/lib/seo-config";
|
||||
import { Phone, CheckCircle2, Shield, Clock, MapPin } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { PublicPageHeader, PublicSurface } from "@/components/public-surface";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Vending Machine Moving & Relocation Services | Rocky Mountain Vending",
|
||||
|
|
@ -32,10 +32,12 @@ export const metadata: Metadata = {
|
|||
|
||||
export default function MovingServicesPage() {
|
||||
return (
|
||||
<PageWrapper maxWidth="6xl">
|
||||
<PageHeader
|
||||
<div className="container mx-auto max-w-6xl px-4 py-10 md:py-14">
|
||||
<PublicPageHeader
|
||||
align="center"
|
||||
eyebrow="Moving 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 */}
|
||||
|
|
@ -248,7 +250,7 @@ export default function MovingServicesPage() {
|
|||
{/* Why Choose Us Section */}
|
||||
<section className="mb-12">
|
||||
<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">
|
||||
<ul className="space-y-4">
|
||||
<li className="flex items-start gap-3">
|
||||
|
|
@ -291,29 +293,27 @@ export default function MovingServicesPage() {
|
|||
|
||||
{/* CTA Section */}
|
||||
<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))' }}>
|
||||
<CardContent className="p-10 md:p-12 text-center">
|
||||
<h2 className="text-2xl md:text-3xl font-bold text-white mb-4 tracking-tight text-balance">
|
||||
<PublicSurface className="p-10 text-center md:p-12">
|
||||
<h2 className="text-2xl md:text-3xl font-bold text-foreground mb-4 tracking-tight text-balance">
|
||||
Ready to Schedule a Hassle-Free Vending Machine Move?
|
||||
</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,
|
||||
and any stairs or special requirements involved. We're here to make your relocation simple and stress-free!
|
||||
</p>
|
||||
<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">
|
||||
<Link href="/#contact">Request a Free Quote</Link>
|
||||
<Button asChild size="lg" className="text-lg h-12 px-8 font-semibold rounded-full">
|
||||
<Link href="/contact-us#contact-form">Request a Quote</Link>
|
||||
</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">
|
||||
<Phone className="w-5 h-5" />
|
||||
{businessConfig.phone}
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</PublicSurface>
|
||||
</section>
|
||||
</PageWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { Card, CardContent } from "@/components/ui/card";
|
|||
import { ReviewsSection } from "@/components/reviews-section";
|
||||
import { Button } from "@/components/ui/button";
|
||||
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";
|
||||
|
||||
interface LocationPageProps {
|
||||
|
|
@ -346,19 +346,27 @@ export default async function LocationPage({ params }: LocationPageProps) {
|
|||
</Card>
|
||||
</div>
|
||||
|
||||
{/* CRM Form */}
|
||||
{/* Placement CTA */}
|
||||
<PublicSurface className="p-6 md:p-8">
|
||||
<div className="text-center mb-6">
|
||||
<h3 className="text-2xl font-bold mb-2">Get Your Free Vending Machine</h3>
|
||||
<p className="text-muted-foreground">
|
||||
Tell us about your location and we'll follow up within one business day to talk through the best machine mix.
|
||||
Open the free placement popup and we'll follow up within one business day to talk through the best machine mix for your location.
|
||||
</p>
|
||||
</div>
|
||||
<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,
|
||||
and any site constraints so our team can recommend the right setup.
|
||||
</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>
|
||||
</section>
|
||||
|
||||
|
|
@ -406,9 +414,7 @@ export default async function LocationPage({ params }: LocationPageProps) {
|
|||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<Button asChild size="lg" className="bg-primary hover:bg-primary/90">
|
||||
<Link href="#contact">Get Your Free Machine Today</Link>
|
||||
</Button>
|
||||
<GetFreeMachineCta buttonLabel="Get Your Free Machine Today" />
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { getPageBySlug } from '@/lib/wordpress-data-loader'
|
|||
import { cleanWordPressContent } from '@/lib/clean-wordPress-content'
|
||||
import type { Metadata } from 'next'
|
||||
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'
|
||||
|
||||
|
|
@ -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>
|
||||
<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">
|
||||
If you're a business looking for placement rather than a purchase, use the request form here and we'll help you sort out the right next step.
|
||||
If you're a business looking for placement rather than a purchase, open the free placement popup and we'll help you sort out the right next step.
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<GetFreeMachineCta buttonLabel="Open Free Placement Form" />
|
||||
</div>
|
||||
</PublicSurface>
|
||||
<PublicSurface>
|
||||
<RequestMachineForm />
|
||||
<PublicSurface className="flex items-center justify-center text-center">
|
||||
<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>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { VendingMachinesShowcase } from '@/components/vending-machines-showcase'
|
|||
import { FeatureCard } from '@/components/feature-card'
|
||||
import type { Metadata } from 'next'
|
||||
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> {
|
||||
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>
|
||||
<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">
|
||||
Use the request form here if you want free placement for a qualifying business and we'll help map out the best machine mix.
|
||||
If you want free placement for a qualifying business, open the Rocky intake popup and we'll help map out the best machine mix without dropping the full form into this page.
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<RequestMachineForm />
|
||||
<div className="mt-6 flex flex-wrap gap-3">
|
||||
<GetFreeMachineCta buttonLabel="Open Free Placement Form" />
|
||||
</div>
|
||||
</PublicSurface>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ export function ContactPage() {
|
|||
</PublicSurface>
|
||||
|
||||
<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)]">
|
||||
<CardContent className="bg-background p-6">
|
||||
<Card className="overflow-hidden rounded-[2rem] border-border/70 bg-white shadow-[0_20px_50px_rgba(0,0,0,0.08)]">
|
||||
<CardContent className="bg-white p-6">
|
||||
<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>
|
||||
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
|
||||
|
|
@ -47,7 +47,7 @@ export function ContactPage() {
|
|||
</p>
|
||||
|
||||
<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">
|
||||
<Phone className="h-5 w-5" />
|
||||
</div>
|
||||
|
|
@ -58,7 +58,7 @@ export function ContactPage() {
|
|||
</div>
|
||||
</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">
|
||||
<Mail className="h-5 w-5" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,65 +1,88 @@
|
|||
"use client"
|
||||
|
||||
import { Phone, MapPin } from "lucide-react"
|
||||
import { ContactForm } from "@/components/forms/contact-form"
|
||||
import Link from "next/link"
|
||||
import { MapPin, Phone, Wrench } from "lucide-react"
|
||||
import { PublicInset, PublicPageHeader, PublicSurface } from "@/components/public-surface"
|
||||
import { businessConfig } from "@/lib/seo-config"
|
||||
|
||||
export function ContactSection() {
|
||||
|
||||
return (
|
||||
<section id="contact" className="py-20 md:py-28 bg-muted/30">
|
||||
<div className="container mx-auto px-4">
|
||||
<PublicPageHeader
|
||||
className="mb-10 max-w-3xl"
|
||||
eyebrow="Detailed Questions"
|
||||
title="Need something more specific? We can sort that out here."
|
||||
description="If you already handled the quick placement request, use this longer form for service details, sales questions, or anything that needs more context."
|
||||
eyebrow="Need Something Else?"
|
||||
title="Repairs, moving, manuals, and sales questions all funnel through one clean contact path."
|
||||
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">
|
||||
<PublicSurface className="space-y-6">
|
||||
<div className="space-y-6">
|
||||
<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">
|
||||
<Phone className="h-6 w-6" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold mb-1 text-foreground">Call or Text Us</div>
|
||||
<a href={businessConfig.publicCallUrl} className="text-muted-foreground hover:text-foreground transition-colors">
|
||||
{businessConfig.publicCallNumber}
|
||||
</a>
|
||||
<p className="text-xs text-muted-foreground mt-1">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>
|
||||
</PublicInset>
|
||||
<div className="grid gap-6 lg:grid-cols-[1.05fr_0.95fr] lg:items-start">
|
||||
<PublicSurface className="p-6 md:p-8">
|
||||
<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>
|
||||
|
||||
<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">
|
||||
<MapPin className="h-6 w-6" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold mb-1 text-foreground">Service Areas</div>
|
||||
<p className="text-muted-foreground">Davis, Salt Lake & Utah Counties</p>
|
||||
<p className="text-xs text-muted-foreground mt-1">Serving 20+ cities across Utah</p>
|
||||
</div>
|
||||
</PublicInset>
|
||||
<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>
|
||||
|
||||
<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 className="space-y-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 shrink-0">
|
||||
<Phone className="h-6 w-6" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold text-foreground">Call or Text Us</div>
|
||||
<a href={businessConfig.publicCallUrl} className="mt-1 block text-muted-foreground transition hover:text-foreground">
|
||||
{businessConfig.publicCallNumber}
|
||||
</a>
|
||||
<p className="mt-1 text-xs text-muted-foreground">Mon-Fri: 8:00 AM - 5:00 PM</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">
|
||||
<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" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold text-foreground">Service Areas</div>
|
||||
<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>
|
||||
</div>
|
||||
</PublicInset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ export function ContactForm({ onSubmit, className, defaultIntent = "" }: Contact
|
|||
<input id="confirm-email" type="email" {...register("confirmEmail")} className="sr-only" tabIndex={-1} />
|
||||
</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
|
||||
eyebrow="Step 1"
|
||||
title="How should we reach you?"
|
||||
|
|
@ -233,7 +233,7 @@ export function ContactForm({ onSubmit, className, defaultIntent = "" }: Contact
|
|||
</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
|
||||
eyebrow="Step 2"
|
||||
title="What do you need help with?"
|
||||
|
|
@ -261,7 +261,7 @@ export function ContactForm({ onSubmit, className, defaultIntent = "" }: Contact
|
|||
/>
|
||||
</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">
|
||||
<Wrench className="h-4 w-4" />
|
||||
</div>
|
||||
|
|
@ -278,7 +278,7 @@ export function ContactForm({ onSubmit, className, defaultIntent = "" }: Contact
|
|||
</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
|
||||
eyebrow="Step 3"
|
||||
title="Tell us the details"
|
||||
|
|
@ -302,26 +302,23 @@ export function ContactForm({ onSubmit, className, defaultIntent = "" }: Contact
|
|||
</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
|
||||
eyebrow="Step 4"
|
||||
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">
|
||||
<SmsConsentFields
|
||||
idPrefix="contact"
|
||||
serviceChecked={Boolean(watchedValues.serviceTextConsent)}
|
||||
marketingChecked={Boolean(watchedValues.marketingTextConsent)}
|
||||
onServiceChange={(checked) => setValue("serviceTextConsent", checked, { shouldValidate: true })}
|
||||
onMarketingChange={(checked) => setValue("marketingTextConsent", checked, { shouldValidate: true })}
|
||||
serviceError={errors.serviceTextConsent?.message}
|
||||
marketingError={errors.marketingTextConsent?.message}
|
||||
/>
|
||||
</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="space-y-1">
|
||||
<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">
|
||||
<a
|
||||
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" />
|
||||
Call
|
||||
</a>
|
||||
<a
|
||||
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" />
|
||||
Text Photos
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const buttonVariants = cva(
|
|||
destructive:
|
||||
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
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:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost:
|
||||
|
|
@ -66,4 +66,4 @@ const FormButton = React.forwardRef<HTMLButtonElement, FormButtonProps>(
|
|||
)
|
||||
FormButton.displayName = "FormButton"
|
||||
|
||||
export { FormButton, buttonVariants }
|
||||
export { FormButton, buttonVariants }
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const FormInput = React.forwardRef<HTMLInputElement, FormInputProps>(
|
|||
type={type}
|
||||
data-slot="input"
|
||||
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",
|
||||
"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" : "",
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ const FormSelect = React.forwardRef<HTMLSelectElement, FormSelectProps>(
|
|||
id={selectId}
|
||||
ref={ref}
|
||||
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",
|
||||
error ? "border-destructive focus:ring-destructive/10" : "",
|
||||
className,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ const FormTextarea = React.forwardRef<HTMLTextAreaElement, FormTextareaProps>(
|
|||
id={textareaId}
|
||||
data-slot="textarea"
|
||||
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",
|
||||
"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" : "",
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ export function RequestMachineForm({ onSubmit, className }: RequestMachineFormPr
|
|||
<input id="confirm-email" type="email" {...register("confirmEmail")} className="sr-only" tabIndex={-1} />
|
||||
</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
|
||||
eyebrow="Step 1"
|
||||
title="Business contact"
|
||||
|
|
@ -274,21 +274,21 @@ export function RequestMachineForm({ onSubmit, className }: RequestMachineFormPr
|
|||
</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
|
||||
eyebrow="Step 2"
|
||||
title="What should we plan for?"
|
||||
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) => {
|
||||
const isChecked = selectedMachineTypes.includes(option.value)
|
||||
return (
|
||||
<label
|
||||
key={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
|
||||
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">
|
||||
<ClipboardCheck className="h-4 w-4 text-primary" />
|
||||
Free placement intake
|
||||
|
|
@ -332,7 +332,7 @@ export function RequestMachineForm({ onSubmit, className }: RequestMachineFormPr
|
|||
</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
|
||||
eyebrow="Step 3"
|
||||
title="Anything else we should know?"
|
||||
|
|
@ -349,21 +349,18 @@ export function RequestMachineForm({ onSubmit, className }: RequestMachineFormPr
|
|||
</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
|
||||
eyebrow="Step 4"
|
||||
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">
|
||||
<SmsConsentFields
|
||||
idPrefix="request-machine"
|
||||
serviceChecked={Boolean(watchedValues.serviceTextConsent)}
|
||||
marketingChecked={Boolean(watchedValues.marketingTextConsent)}
|
||||
onServiceChange={(checked) => setValue("serviceTextConsent", checked, { shouldValidate: true })}
|
||||
onMarketingChange={(checked) => setValue("marketingTextConsent", checked, { shouldValidate: true })}
|
||||
serviceError={errors.serviceTextConsent?.message}
|
||||
marketingError={errors.marketingTextConsent?.message}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -383,7 +380,7 @@ export function RequestMachineForm({ onSubmit, className }: RequestMachineFormPr
|
|||
</FormButton>
|
||||
<a
|
||||
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" />
|
||||
Call
|
||||
|
|
|
|||
|
|
@ -32,10 +32,10 @@ function PolicyLinks() {
|
|||
|
||||
type SmsConsentFieldsProps = {
|
||||
idPrefix: string
|
||||
marketingChecked: boolean
|
||||
marketingChecked?: boolean
|
||||
marketingError?: string
|
||||
mode?: "chat" | "forms"
|
||||
onMarketingChange: (checked: boolean) => void
|
||||
onMarketingChange?: (checked: boolean) => void
|
||||
onServiceChange: (checked: boolean) => void
|
||||
serviceChecked: boolean
|
||||
serviceError?: string
|
||||
|
|
@ -43,17 +43,17 @@ type SmsConsentFieldsProps = {
|
|||
|
||||
export function SmsConsentFields({
|
||||
idPrefix,
|
||||
marketingChecked,
|
||||
marketingError,
|
||||
marketingChecked: _marketingChecked,
|
||||
marketingError: _marketingError,
|
||||
mode = "forms",
|
||||
onMarketingChange,
|
||||
onMarketingChange: _onMarketingChange,
|
||||
onServiceChange,
|
||||
serviceChecked,
|
||||
serviceError,
|
||||
}: SmsConsentFieldsProps) {
|
||||
return (
|
||||
<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
|
||||
id={`${idPrefix}-service-consent`}
|
||||
checked={serviceChecked}
|
||||
|
|
@ -64,36 +64,13 @@ export function SmsConsentFields({
|
|||
htmlFor={`${idPrefix}-service-consent`}
|
||||
className="text-xs leading-relaxed text-muted-foreground"
|
||||
>
|
||||
I agree to receive conversational SMS from {businessConfig.legalName} about my inquiry, scheduling,
|
||||
support, repairs, moving, and follow-up. 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.
|
||||
I agree to Terms & Conditions provided by {businessConfig.legalName}. By providing my phone number, I agree
|
||||
to receive text messages about my inquiry, scheduling, support, repairs, moving, and follow-up. Message
|
||||
frequency varies. Message and data rates may apply. Reply STOP to opt out and HELP for help.
|
||||
<PolicyLinks />
|
||||
</Label>
|
||||
</div>
|
||||
{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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
37
components/get-free-machine-cta.tsx
Normal file
37
components/get-free-machine-cta.tsx
Normal 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} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -565,7 +565,7 @@ export function ManualsPageClient({
|
|||
placeholder="Search manuals by name, manufacturer, or category..."
|
||||
value={searchTerm}
|
||||
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>
|
||||
|
||||
|
|
@ -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" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
|
@ -614,7 +614,7 @@ export function ManualsPageClient({
|
|||
}}
|
||||
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" />
|
||||
</SelectTrigger>
|
||||
<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 items-center gap-3 w-full sm:w-auto">
|
||||
<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
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export function PublicSurface({
|
|||
return (
|
||||
<Component
|
||||
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,
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -72,7 +72,7 @@ export function PublicInset({
|
|||
return (
|
||||
<div
|
||||
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,
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export function RequestMachineSection() {
|
|||
<section id="request-machine" className="bg-background py-16 md:py-24">
|
||||
<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">
|
||||
<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">
|
||||
<Package className="h-4 w-4" />
|
||||
Free Placement
|
||||
|
|
@ -33,7 +33,7 @@ export function RequestMachineSection() {
|
|||
</PublicInset>
|
||||
|
||||
<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
|
||||
</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">
|
||||
|
|
@ -43,7 +43,7 @@ export function RequestMachineSection() {
|
|||
</div>
|
||||
</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)} />
|
||||
</PublicSurface>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -141,11 +141,11 @@ export function ReviewsPage() {
|
|||
</p>
|
||||
</div>
|
||||
<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>
|
||||
<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 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>
|
||||
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">Reach out about repairs, moving, manuals, parts, or machine sales.</p>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -398,7 +398,7 @@ export function SiteChatWidget() {
|
|||
{isOpen ? (
|
||||
<div
|
||||
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 }}
|
||||
>
|
||||
<div className="flex items-start justify-between border-b border-border/70 px-4 py-4">
|
||||
|
|
@ -412,7 +412,7 @@ export function SiteChatWidget() {
|
|||
<button
|
||||
type="button"
|
||||
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"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
|
|
@ -422,7 +422,7 @@ export function SiteChatWidget() {
|
|||
{!profile ? (
|
||||
<div className="min-h-0 overflow-y-auto px-4 py-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="mt-1 text-xs leading-relaxed text-muted-foreground">
|
||||
We use this to route the conversation to the right team member.
|
||||
|
|
@ -469,7 +469,7 @@ export function SiteChatWidget() {
|
|||
</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="mt-1 text-xs leading-relaxed text-muted-foreground">
|
||||
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"
|
||||
mode="chat"
|
||||
serviceChecked={profileDraft.serviceTextConsent}
|
||||
marketingChecked={profileDraft.marketingTextConsent}
|
||||
onServiceChange={(checked) =>
|
||||
setProfileDraft((current) => ({
|
||||
...current,
|
||||
|
|
@ -488,7 +487,6 @@ export function SiteChatWidget() {
|
|||
consentSourcePage: pathname || "/",
|
||||
}))
|
||||
}
|
||||
onMarketingChange={() => undefined}
|
||||
serviceError={profileError && !profileDraft.serviceTextConsent ? profileError : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -506,7 +504,7 @@ export function SiteChatWidget() {
|
|||
|
||||
<a
|
||||
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" />
|
||||
Call
|
||||
|
|
@ -571,7 +569,7 @@ export function SiteChatWidget() {
|
|||
</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">
|
||||
<label htmlFor="site-chat-input" className="text-sm font-semibold text-foreground">
|
||||
Message
|
||||
|
|
@ -585,7 +583,7 @@ export function SiteChatWidget() {
|
|||
placeholder="Describe what you need"
|
||||
rows={3}
|
||||
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
|
||||
|
|
@ -609,7 +607,7 @@ export function SiteChatWidget() {
|
|||
|
||||
<a
|
||||
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" />
|
||||
Call
|
||||
|
|
@ -636,7 +634,7 @@ export function SiteChatWidget() {
|
|||
type="button"
|
||||
data-testid="site-chat-launcher"
|
||||
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}`}
|
||||
>
|
||||
<AssistantAvatar src={bootstrap.avatarSrc} alt={bootstrap.assistantName} sizeClassName="h-12 w-12" />
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const buttonVariants = cva(
|
|||
destructive:
|
||||
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
||||
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:
|
||||
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost:
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<'div'>) {
|
|||
<div
|
||||
data-slot="card"
|
||||
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,
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { VendingMachinesShowcase } from "@/components/vending-machines-showcase"
|
|||
import { CheckCircle2, CreditCard, MonitorSmartphone, Package, Wrench } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { PublicInset, PublicPageHeader, PublicSurface } from "@/components/public-surface"
|
||||
import { GetFreeMachineCta } from "@/components/get-free-machine-cta"
|
||||
|
||||
export function VendingMachinesPage() {
|
||||
return (
|
||||
|
|
@ -30,7 +31,7 @@ export function VendingMachinesPage() {
|
|||
["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."],
|
||||
].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" />
|
||||
<div>
|
||||
<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">
|
||||
<Link href="/contact-us#contact-form">Contact Us</Link>
|
||||
</Button>
|
||||
<Button asChild size="lg" variant="outline" className="h-11 rounded-full px-6">
|
||||
<Link href="/#request-machine">Get Free Machine</Link>
|
||||
</Button>
|
||||
<GetFreeMachineCta buttonLabel="Get Free Machine" className="h-11 px-6" variant="outline" />
|
||||
</div>
|
||||
</PublicSurface>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { ReactNode } from "react"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { GetFreeMachineCta } from "@/components/get-free-machine-cta"
|
||||
import Link from "next/link"
|
||||
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">
|
||||
<Link href="/contact-us">Contact Us</Link>
|
||||
</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">
|
||||
<Link href="/#request-machine">Get Free Machine</Link>
|
||||
</Button>
|
||||
<GetFreeMachineCta
|
||||
buttonLabel="Get Free Machine"
|
||||
className="h-12 border-2 border-white bg-transparent px-8 text-lg font-semibold text-white hover:bg-white/10"
|
||||
variant="outline"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
@ -137,4 +140,3 @@ export function WhoWeServePage({ title, content }: WhoWeServePageProps) {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue