Deploy
This commit is contained in:
@@ -0,0 +1,143 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import Image from "next/image"
|
||||||
|
import Link from "next/link"
|
||||||
|
import { Activity, ArrowRight } from 'lucide-react'
|
||||||
|
import { useProducts } from "@/hooks/useProducts"
|
||||||
|
import Button from "@/components/ui/Button"
|
||||||
|
import { Card } from "@/components/ui/Card"
|
||||||
|
|
||||||
|
const CURRENCY_SYMBOLS: Record<string, string> = {
|
||||||
|
zar: "R",
|
||||||
|
usd: "$",
|
||||||
|
eur: "€",
|
||||||
|
gbp: "£",
|
||||||
|
kes: "KSh",
|
||||||
|
ngn: "₦",
|
||||||
|
ghs: "₵",
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatPrice = (amount: number, code?: string) => {
|
||||||
|
const symbol = CURRENCY_SYMBOLS[(code ?? "zar").toLowerCase()] ?? (code ? code.toUpperCase() : "R")
|
||||||
|
return symbol + (amount / 100).toFixed(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditions = [
|
||||||
|
"Neck, back and joint pain",
|
||||||
|
"Headaches and migraines",
|
||||||
|
"Nerve impingement",
|
||||||
|
"Sports and accident recovery",
|
||||||
|
"Spinal decompression",
|
||||||
|
"Fibromyalgia and infant colic",
|
||||||
|
]
|
||||||
|
|
||||||
|
const skeletonKeys = ["one", "two", "three", "four"]
|
||||||
|
|
||||||
|
export default function ChiropracticServicesSection() {
|
||||||
|
const { products, loading } = useProducts(8)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section data-vula-section="chiropracticservices" id="chiropractic-services" className="bg-background py-20 text-foreground lg:py-28">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="max-w-3xl">
|
||||||
|
<p className="text-sm font-semibold uppercase tracking-[0.18em] text-primary">Services</p>
|
||||||
|
<h2 className="mt-4 text-3xl font-bold leading-tight sm:text-4xl">
|
||||||
|
A plan shaped by <span className="text-primary">your body</span>, not a queue
|
||||||
|
</h2>
|
||||||
|
<p className="mt-4 max-w-[44ch] text-lg text-muted-foreground">
|
||||||
|
Book the appointment that fits. Every session opens with a proper diagnosis and closes
|
||||||
|
with something you can do at home before the next visit.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-muted-foreground">
|
||||||
|
{conditions.map((condition) => (
|
||||||
|
<span key={condition} className="flex items-center gap-2">
|
||||||
|
<span className="h-1.5 w-1.5 rounded-full bg-secondary" />
|
||||||
|
{condition}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<div className="mt-12 grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
|
{skeletonKeys.map((key) => (
|
||||||
|
<div key={key} className="overflow-hidden rounded-lg bg-card shadow-md">
|
||||||
|
<div className="aspect-video w-full animate-pulse bg-muted" />
|
||||||
|
<div className="space-y-3 p-6">
|
||||||
|
<div className="h-4 w-2/3 animate-pulse rounded bg-muted" />
|
||||||
|
<div className="h-3 w-full animate-pulse rounded bg-muted" />
|
||||||
|
<div className="h-3 w-4/5 animate-pulse rounded bg-muted" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="mt-12 grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
|
{products.map((service) => {
|
||||||
|
const price = service.variants?.[0]?.prices?.[0]
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
key={service.id}
|
||||||
|
className="group overflow-hidden rounded-lg shadow-md transition-all duration-200 ease-out hover:-translate-y-1 hover:shadow-xl"
|
||||||
|
>
|
||||||
|
<Link href={"/booking/" + service.id} className="block cursor-pointer">
|
||||||
|
<div className="relative aspect-video w-full overflow-hidden bg-muted">
|
||||||
|
{service.thumbnail ? (
|
||||||
|
<Image
|
||||||
|
src={service.thumbnail}
|
||||||
|
alt={service.title}
|
||||||
|
fill
|
||||||
|
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw"
|
||||||
|
className="object-cover transition-transform duration-500 ease-out group-hover:scale-105"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex h-full w-full items-center justify-center text-muted-foreground">
|
||||||
|
<Activity className="h-8 w-8" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="p-6">
|
||||||
|
<h3 className="text-lg font-semibold leading-snug">{service.title}</h3>
|
||||||
|
{service.description ? (
|
||||||
|
<p className="mt-2 line-clamp-2 text-sm text-muted-foreground">
|
||||||
|
{service.description}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
<div className="mt-4 flex items-center justify-between gap-3">
|
||||||
|
{price ? (
|
||||||
|
<span className="text-base font-semibold text-primary">
|
||||||
|
{formatPrice(price.amount, price.currency_code)}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-sm text-muted-foreground">Enquire at booking</span>
|
||||||
|
)}
|
||||||
|
<span className="inline-flex items-center gap-1 text-sm font-medium text-primary">
|
||||||
|
Book
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mt-12 flex flex-col items-start gap-4 border-t border-border pt-8 sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<p className="max-w-[44ch] text-sm text-muted-foreground">
|
||||||
|
Choose a time online — the practice confirms it with you. Babies from six weeks,
|
||||||
|
pregnant moms, athletes and desk workers are all welcome.
|
||||||
|
</p>
|
||||||
|
<Button asChild variant="secondary">
|
||||||
|
<Link href="/services">
|
||||||
|
See all services
|
||||||
|
<ArrowRight className="ml-2 h-4 w-4" />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user