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:
asownerytandClaude Opus 5.5 committed 2026-09-30 20:37:42 +13:00
1 parent 7043b47de1
commit 40d3a08050
13 files changed
+502 -236

No files matched your search

+182
View File
@@ -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&apos;t fix it, you don&apos;t pay it.
Once I know what&apos;s wrong, I&apos;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&apos;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">
&ldquo;{testimonial.quote}&rdquo;
</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>
);
}