Phase 3: service pages on a shared template
- ServicePage template: H1 with service + area, intro with Call/Text above the fold, What I fix, Pricing, How it works, Why choose me, a testimonial, FAQs (with FAQPage schema), CTA band, related services - Service copy moved to src/lib/services.js, rewritten in first person with owner-confirmed jobs and prices only; removes the "team of technicians", "preferred choice", Mac and motherboard claims - FaqList and CtaBand extracted and shared with the homepage - Button gains primary/outlineDark variants; responsive page-header H1 - Compact Chatwoot bubble on phones so it doesn't cover CTAs Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
7043b47de1
commit
40d3a08050
13 files changed
+502
-236
No files matched your search
@@ -0,0 +1,182 @@
|
||||
import Link from "next/link";
|
||||
import { ArrowRight, Check, MessageSquare, Phone } from "lucide-react";
|
||||
import HeaderSm from "./HeaderSm";
|
||||
import Button from "./Button";
|
||||
import CtaBand from "./CtaBand";
|
||||
import FaqList from "./FaqList";
|
||||
import JsonLd from "./JsonLd";
|
||||
import { business, links, pageMetadata, serviceSchema } from "@/lib/site";
|
||||
import { services } from "@/lib/services";
|
||||
|
||||
const promises = [
|
||||
{
|
||||
title: "No fix, no fee",
|
||||
text: "If I can't fix it, you don't pay the $35 diagnosis.",
|
||||
},
|
||||
{
|
||||
title: "Price before I start",
|
||||
text: "I'll tell you what's wrong and what it costs before any work begins.",
|
||||
},
|
||||
{
|
||||
title: "You deal with me",
|
||||
text: "The person you talk to is the person fixing your computer.",
|
||||
},
|
||||
{
|
||||
title: "Local to Te Atatū",
|
||||
text: "Based on Te Atatū Peninsula, serving West Auckland and the CBD.",
|
||||
},
|
||||
];
|
||||
|
||||
export function serviceMetadata(slug) {
|
||||
const { title, description } = services[slug];
|
||||
return pageMetadata({ title, description, path: `/services/${slug}` });
|
||||
}
|
||||
|
||||
export default function ServicePage({ slug }) {
|
||||
const service = services[slug];
|
||||
const path = `/services/${slug}`;
|
||||
const testimonial = business.testimonials.find(
|
||||
({ name }) => name === service.testimonial
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<JsonLd
|
||||
data={serviceSchema({
|
||||
name: service.name,
|
||||
description: service.description,
|
||||
path,
|
||||
})}
|
||||
/>
|
||||
<HeaderSm text={service.h1} />
|
||||
|
||||
<section className="container mx-auto max-w-3xl mb-16">
|
||||
{service.intro.map((p) => (
|
||||
<p key={p} className="text-lg mb-4">
|
||||
{p}
|
||||
</p>
|
||||
))}
|
||||
<div className="flex flex-col sm:flex-row gap-3 mt-6">
|
||||
<Button href={links.tel} variant="primary">
|
||||
<Phone aria-hidden="true" />
|
||||
Call {business.phone}
|
||||
</Button>
|
||||
<Button href={links.sms} variant="outlineDark">
|
||||
<MessageSquare aria-hidden="true" />
|
||||
Text me
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="container mx-auto max-w-3xl mb-16">
|
||||
<h2 className="text-3xl font-bold mb-4">What I fix</h2>
|
||||
<ul className="space-y-2">
|
||||
{service.fixes.map((fix) => (
|
||||
<li key={fix} className="flex gap-2">
|
||||
<Check
|
||||
aria-hidden="true"
|
||||
className="size-5 shrink-0 mt-0.5 text-sky-800"
|
||||
/>
|
||||
{fix}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{service.fixesNote && <p className="mt-4">{service.fixesNote}</p>}
|
||||
</section>
|
||||
|
||||
<section className="container mx-auto max-w-3xl mb-16">
|
||||
<h2 className="text-3xl font-bold mb-4">Pricing</h2>
|
||||
<table className="w-full mb-4 text-left">
|
||||
<tbody>
|
||||
{service.prices.map(({ item, price }) => (
|
||||
<tr key={item} className="border-b">
|
||||
<th scope="row" className="py-3 pr-4 font-normal">
|
||||
{item}
|
||||
</th>
|
||||
<td className="py-3 font-bold text-sky-800">{price}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<p className="mb-2">
|
||||
The diagnosis is $35. If I can't fix it, you don't pay it.
|
||||
Once I know what's wrong, I'll tell you what the repair will
|
||||
cost before I start. If you decide not to go ahead, the $35 still
|
||||
applies.
|
||||
</p>
|
||||
<p className="text-slate-600">
|
||||
Labour is $35 an hour. Parts are extra.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="container mx-auto max-w-3xl mb-16">
|
||||
<h2 className="text-3xl font-bold mb-4">How it works</h2>
|
||||
<ul className="list-disc pl-6 space-y-2 mb-4">
|
||||
<li>
|
||||
<strong>Drop-off:</strong> bring it to {business.address.street},{" "}
|
||||
{business.address.suburb}, by appointment.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Pickup or onsite:</strong> I can collect it or come to you
|
||||
anywhere in my service area. The travel fee depends on distance.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Remote:</strong> many software problems can be fixed over
|
||||
the internet.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Most repairs are done within 24–48 hours, although this isn't
|
||||
guaranteed. Parts that need ordering usually take about a week. I
|
||||
work Monday to Friday, 9am to 6pm, and weekends by appointment.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="bg-slate-100 py-16 mb-16">
|
||||
<div className="container mx-auto max-w-3xl">
|
||||
<h2 className="text-3xl font-bold mb-6">Why choose me</h2>
|
||||
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||
{promises.map(({ title, text }) => (
|
||||
<li key={title}>
|
||||
<h3 className="font-bold text-lg mb-1">{title}</h3>
|
||||
<p>{text}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{testimonial && (
|
||||
<figure className="mt-8 rounded-lg border bg-white p-6">
|
||||
<blockquote className="mb-3">
|
||||
“{testimonial.quote}”
|
||||
</blockquote>
|
||||
<figcaption className="font-bold">{testimonial.name}</figcaption>
|
||||
</figure>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="container mx-auto max-w-3xl mb-16">
|
||||
<h2 className="text-3xl font-bold mb-4">Questions</h2>
|
||||
<FaqList faqs={service.faqs} />
|
||||
</section>
|
||||
|
||||
<CtaBand />
|
||||
|
||||
<section className="container mx-auto max-w-3xl py-16">
|
||||
<h2 className="text-2xl font-bold mb-4">Related services</h2>
|
||||
<ul className="space-y-2">
|
||||
{service.related.map((related) => (
|
||||
<li key={related}>
|
||||
<Link
|
||||
href={`/services/${related}`}
|
||||
className="inline-flex items-center gap-1 font-bold text-sky-800 hover:underline"
|
||||
>
|
||||
{services[related].name}
|
||||
<ArrowRight aria-hidden="true" className="size-4" />
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user