Rocky_Mountain_Vending/components/header.tsx
DMleadgen 46d973904b
Initial commit: Rocky Mountain Vending website
Next.js website for Rocky Mountain Vending company featuring:
- Product catalog with Stripe integration
- Service areas and parts pages
- Admin dashboard with Clerk authentication
- SEO optimized pages with JSON-LD structured data

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 16:22:15 -07:00

493 lines
20 KiB
TypeScript

"use client"
import { useReducer } from "react"
import Link from "next/link"
import Image from "next/image"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Menu, X, Phone, ChevronDown } from "lucide-react"
import { GetFreeMachineModal } from "@/components/get-free-machine-modal"
import { Cart } from "@/components/cart"
import { CartButton } from "@/components/cart-button"
import { MobileCartButton } from "@/components/mobile-cart-button"
// Header state types following Vercel React Best Practices
type HeaderState = {
isMenuOpen: boolean
isWhoWeServeOpen: boolean
isVendingMachinesOpen: boolean
isFoodBeverageOpen: boolean
isServicesOpen: boolean
isBlogPostsOpen: boolean
isAboutOpen: boolean
isModalOpen: boolean
isCartOpen: boolean
}
type HeaderAction =
| { type: 'TOGGLE_MENU' }
| { type: 'TOGGLE_WHO_WE_SERVE' }
| { type: 'TOGGLE_VENDING_MACHINES' }
| { type: 'TOGGLE_FOOD_BEVERAGE' }
| { type: 'TOGGLE_SERVICES' }
| { type: 'TOGGLE_BLOG_POSTS' }
| { type: 'TOGGLE_ABOUT' }
| { type: 'SET_MODAL'; value: boolean }
| { type: 'SET_CART'; value: boolean }
// Reducer for header state - consolidates related mobile menu states
function headerReducer(state: HeaderState, action: HeaderAction): HeaderState {
switch (action.type) {
case 'TOGGLE_MENU':
return { ...state, isMenuOpen: !state.isMenuOpen }
case 'TOGGLE_WHO_WE_SERVE':
return { ...state, isWhoWeServeOpen: !state.isWhoWeServeOpen }
case 'TOGGLE_VENDING_MACHINES':
return { ...state, isVendingMachinesOpen: !state.isVendingMachinesOpen }
case 'TOGGLE_FOOD_BEVERAGE':
return { ...state, isFoodBeverageOpen: !state.isFoodBeverageOpen }
case 'TOGGLE_SERVICES':
return { ...state, isServicesOpen: !state.isServicesOpen }
case 'TOGGLE_BLOG_POSTS':
return { ...state, isBlogPostsOpen: !state.isBlogPostsOpen }
case 'TOGGLE_ABOUT':
return { ...state, isAboutOpen: !state.isAboutOpen }
case 'SET_MODAL':
return { ...state, isModalOpen: action.value }
case 'SET_CART':
return { ...state, isCartOpen: action.value }
default:
return state
}
}
export function Header() {
const [state, dispatch] = useReducer(headerReducer, {
isMenuOpen: false,
isWhoWeServeOpen: false,
isVendingMachinesOpen: false,
isFoodBeverageOpen: false,
isServicesOpen: false,
isBlogPostsOpen: false,
isAboutOpen: false,
isModalOpen: false,
isCartOpen: false,
})
const whoWeServeItems = [
{ label: "Warehouses", href: "/warehouses" },
{ label: "Auto Repair", href: "/auto-repair" },
{ label: "Gyms", href: "/gyms" },
{ label: "Community Centers", href: "/community-centers" },
{ label: "Dance Studios", href: "/dance-studios" },
{ label: "Car Washes", href: "/car-washes" },
]
const vendingMachinesItems = [
{ label: "Machines We Use", href: "/vending-machines/machines-we-use" },
{ label: "Machines for Sale in Utah", href: "/vending-machines/machines-for-sale" },
{ label: "Vending Machine Manuals", href: "/manuals" },
]
const foodBeverageItems = [
{ label: "Healthy Options", href: "/food-and-beverage/healthy-options" },
{ label: "Traditional Options", href: "/food-and-beverage/traditional-options" },
{ label: "Food & Beverage Suppliers", href: "/food-and-beverage/suppliers" },
]
const servicesItems = [
{ label: "Vending Machine Repairs", href: "/services/repairs" },
{ label: "Vending Machine Moving Services", href: "/services/moving" },
{ label: "Vending Machine Parts", href: "/services/parts" },
{ label: "Vending Machine Manuals", href: "/manuals" },
]
const blogPostsItems = [
{ label: "Seaga HY900 Support", href: "/seaga-hy900-support" },
]
const aboutItems = [
{ label: "About Us", href: "/about-us" },
{ label: "Reviews", href: "/reviews" },
{ label: "FAQs", href: "/about/faqs" },
]
return (
<header className="sticky top-0 z-40 w-full border-b border-border/40 bg-white/95 backdrop-blur supports-[backdrop-filter]:bg-white/90 shadow-sm">
<div className="w-full px-4 lg:px-6 xl:px-8 2xl:px-12">
<div className="flex h-20 items-center justify-between gap-4 lg:gap-6">
{/* Logo */}
<Link href="/" className="flex items-center gap-2 flex-shrink-0 min-w-0">
<Image
src="/rmv-logo.png"
alt="Rocky Mountain Vending"
width={220}
height={55}
className="h-12 sm:h-14 w-auto object-contain"
priority
/>
</Link>
{/* Desktop Navigation */}
<nav className="hidden items-center gap-4 lg:gap-6 md:flex flex-1 justify-center">
<Link href="/" className="text-sm font-medium transition-colors">
Home
</Link>
{/* Who We Serve Dropdown */}
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center gap-1.5 text-sm font-medium hover-brand outline-none data-[state=open]:text-primary">
Who We Serve
<ChevronDown className="h-4 w-4" aria-hidden="true" />
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-52">
{whoWeServeItems.map((item) => (
<DropdownMenuItem key={item.href} asChild>
<Link href={item.href}>{item.label}</Link>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
{/* Vending Machines Dropdown */}
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center gap-1.5 text-sm font-medium hover-brand outline-none data-[state=open]:text-primary">
Vending Machines
<ChevronDown className="h-4 w-4" aria-hidden="true" />
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-60">
{vendingMachinesItems.map((item) => (
<DropdownMenuItem key={item.href} asChild>
<Link href={item.href}>{item.label}</Link>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
{/* Food and Beverage Dropdown */}
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center gap-1.5 text-sm font-medium hover-brand whitespace-nowrap outline-none data-[state=open]:text-primary">
Food & Beverage
<ChevronDown className="h-4 w-4" aria-hidden="true" />
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-60">
{foodBeverageItems.map((item) => (
<DropdownMenuItem key={item.href} asChild>
<Link href={item.href}>{item.label}</Link>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
{/* Services Dropdown */}
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center gap-1.5 text-sm font-medium hover-brand outline-none data-[state=open]:text-primary">
Services
<ChevronDown className="h-4 w-4" aria-hidden="true" />
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-72">
{servicesItems.map((item) => (
<DropdownMenuItem key={item.href} asChild>
<Link href={item.href}>{item.label}</Link>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
{/* Blog Posts Dropdown */}
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center gap-1.5 text-sm font-medium hover-brand outline-none data-[state=open]:text-primary">
Blog Posts
<ChevronDown className="h-4 w-4" />
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-52">
{blogPostsItems.map((item) => (
<DropdownMenuItem key={item.href} asChild>
<Link href={item.href}>{item.label}</Link>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
{/* About Us Dropdown */}
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center gap-1.5 text-sm font-medium hover-brand outline-none data-[state=open]:text-primary">
About Us
<ChevronDown className="h-4 w-4" />
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-52">
{aboutItems.map((item) => (
<DropdownMenuItem key={item.href} asChild>
<Link href={item.href}>{item.label}</Link>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<Link
href="/products"
className="text-sm font-medium transition-colors"
>
Products
</Link>
<Link
href="/contact-us"
className="text-sm font-medium transition-colors"
>
Contact Us
</Link>
</nav>
{/* Desktop CTA */}
<div className="hidden items-center gap-2 lg:gap-3 md:flex flex-shrink-0">
<CartButton onClick={() => dispatch({ type: 'SET_CART', value: true })} />
<a
href="tel:+14352339668"
className="flex items-center gap-2 text-sm font-medium transition-colors whitespace-nowrap"
>
<Phone className="h-4 w-4 flex-shrink-0" />
<span className="hidden lg:inline">(435) 233-9668</span>
</a>
<Button
onClick={() => dispatch({ type: 'SET_MODAL', value: true })}
className="bg-primary hover:bg-primary/90 whitespace-nowrap"
size="sm"
>
Get Free Machine
</Button>
</div>
{/* Mobile Menu Button */}
<button className="md:hidden" onClick={() => dispatch({ type: 'TOGGLE_MENU' })} aria-label="Toggle menu">
{state.isMenuOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
</button>
</div>
{/* Mobile Navigation */}
{state.isMenuOpen && (
<nav className="flex flex-col gap-5 py-6 md:hidden border-t border-border/40">
<Link
href="/"
className="text-sm font-medium py-1 transition-colors"
onClick={() => dispatch({ type: 'TOGGLE_MENU' })}
>
Home
</Link>
{/* Who We Serve Mobile Section */}
<div className="flex flex-col gap-3">
<button
onClick={() => dispatch({ type: 'TOGGLE_WHO_WE_SERVE' })}
className="flex items-center justify-between text-sm font-medium py-1 hover-brand"
aria-label="Who We Serve menu"
aria-expanded={state.isWhoWeServeOpen}
aria-haspopup="true"
>
Who We Serve
<ChevronDown className={`h-4 w-4 transition-transform ${state.isWhoWeServeOpen ? "rotate-180" : ""}`} aria-hidden="true" />
</button>
{state.isWhoWeServeOpen && (
<div className="flex flex-col gap-3 pl-4 border-l-2 border-secondary/30">
{whoWeServeItems.map((item) => (
<Link
key={item.href}
href={item.href}
className="text-sm py-1 transition-colors"
onClick={() => dispatch({ type: 'TOGGLE_MENU' })}
>
{item.label}
</Link>
))}
</div>
)}
</div>
{/* Vending Machines Mobile */}
<div className="flex flex-col gap-3">
<button
onClick={() => dispatch({ type: 'TOGGLE_VENDING_MACHINES' })}
className="flex items-center justify-between text-sm font-medium py-1 hover-brand"
aria-label="Vending Machines menu"
aria-expanded={state.isVendingMachinesOpen}
aria-haspopup="true"
>
Vending Machines
<ChevronDown className={`h-4 w-4 transition-transform ${state.isVendingMachinesOpen ? "rotate-180" : ""}`} aria-hidden="true" />
</button>
{state.isVendingMachinesOpen && (
<div className="flex flex-col gap-3 pl-4 border-l-2 border-secondary/30">
{vendingMachinesItems.map((item) => (
<Link
key={item.href}
href={item.href}
className="text-sm py-1 transition-colors"
onClick={() => dispatch({ type: 'TOGGLE_MENU' })}
>
{item.label}
</Link>
))}
</div>
)}
</div>
{/* Food and Beverage Mobile */}
<div className="flex flex-col gap-3">
<button
onClick={() => dispatch({ type: 'TOGGLE_FOOD_BEVERAGE' })}
className="flex items-center justify-between text-sm font-medium py-1 hover-brand"
aria-label="Food & Beverage menu"
aria-expanded={state.isFoodBeverageOpen}
aria-haspopup="true"
>
Food & Beverage
<ChevronDown className={`h-4 w-4 transition-transform ${state.isFoodBeverageOpen ? "rotate-180" : ""}`} aria-hidden="true" />
</button>
{state.isFoodBeverageOpen && (
<div className="flex flex-col gap-3 pl-4 border-l-2 border-secondary/30">
{foodBeverageItems.map((item) => (
<Link
key={item.href}
href={item.href}
className="text-sm py-1 transition-colors"
onClick={() => dispatch({ type: 'TOGGLE_MENU' })}
>
{item.label}
</Link>
))}
</div>
)}
</div>
{/* Services Mobile */}
<div className="flex flex-col gap-3">
<button
onClick={() => dispatch({ type: 'TOGGLE_SERVICES' })}
className="flex items-center justify-between text-sm font-medium py-1 hover-brand"
aria-label="Services menu"
aria-expanded={state.isServicesOpen}
aria-haspopup="true"
>
Services
<ChevronDown className={`h-4 w-4 transition-transform ${state.isServicesOpen ? "rotate-180" : ""}`} aria-hidden="true" />
</button>
{state.isServicesOpen && (
<div className="flex flex-col gap-3 pl-4 border-l-2 border-secondary/30">
{servicesItems.map((item) => (
<Link
key={item.href}
href={item.href}
className="text-sm py-1 transition-colors"
onClick={() => dispatch({ type: 'TOGGLE_MENU' })}
>
{item.label}
</Link>
))}
</div>
)}
</div>
{/* Blog Posts Mobile */}
<div className="flex flex-col gap-3">
<button
onClick={() => dispatch({ type: 'TOGGLE_BLOG_POSTS' })}
className="flex items-center justify-between text-sm font-medium py-1 hover-brand"
aria-label="Blog Posts menu"
aria-expanded={state.isBlogPostsOpen}
aria-haspopup="true"
>
Blog Posts
<ChevronDown className={`h-4 w-4 transition-transform ${state.isBlogPostsOpen ? "rotate-180" : ""}`} aria-hidden="true" />
</button>
{state.isBlogPostsOpen && (
<div className="flex flex-col gap-3 pl-4 border-l-2 border-secondary/30">
{blogPostsItems.map((item) => (
<Link
key={item.href}
href={item.href}
className="text-sm py-1 transition-colors"
onClick={() => dispatch({ type: 'TOGGLE_MENU' })}
>
{item.label}
</Link>
))}
</div>
)}
</div>
{/* About Us Mobile */}
<div className="flex flex-col gap-3">
<button
onClick={() => dispatch({ type: 'TOGGLE_ABOUT' })}
className="flex items-center justify-between text-sm font-medium py-1 hover-brand"
aria-label="About menu"
aria-expanded={state.isAboutOpen}
aria-haspopup="true"
>
About Us
<ChevronDown className={`h-4 w-4 transition-transform ${state.isAboutOpen ? "rotate-180" : ""}`} aria-hidden="true" />
</button>
{state.isAboutOpen && (
<div className="flex flex-col gap-3 pl-4 border-l-2 border-secondary/30">
{aboutItems.map((item) => (
<Link
key={item.href}
href={item.href}
className="text-sm py-1 transition-colors"
onClick={() => dispatch({ type: 'TOGGLE_MENU' })}
>
{item.label}
</Link>
))}
</div>
)}
</div>
<Link
href="/products"
className="text-sm font-medium py-1 transition-colors"
onClick={() => dispatch({ type: 'TOGGLE_MENU' })}
>
Products
</Link>
<Link
href="/contact-us"
className="text-sm font-medium py-1 transition-colors"
onClick={() => dispatch({ type: 'TOGGLE_MENU' })}
>
Contact Us
</Link>
<div className="flex flex-col gap-4 pt-4 border-t border-border/40">
<MobileCartButton
onClick={() => {
dispatch({ type: 'TOGGLE_MENU' })
dispatch({ type: 'SET_CART', value: true })
}}
/>
<a href="tel:+14352339668" className="flex items-center gap-2 text-sm font-medium transition-colors">
<Phone className="h-4 w-4" />
<span>(435) 233-9668</span>
</a>
<Button
onClick={() => {
dispatch({ type: 'TOGGLE_MENU' })
dispatch({ type: 'SET_MODAL', value: true })
}}
className="bg-primary hover:bg-primary/90 w-full"
>
Get Free Machine
</Button>
</div>
</nav>
)}
</div>
<GetFreeMachineModal open={state.isModalOpen} onOpenChange={(value) => dispatch({ type: 'SET_MODAL', value })} />
<Cart isOpen={state.isCartOpen} onClose={() => dispatch({ type: 'SET_CART', value: false })} />
</header>
)
}