181 lines
6.6 KiB
TypeScript
181 lines
6.6 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import { useTranslation, Link } from "@/i18n/navigation";
|
||
import Footer from "@/app/components/Footer";
|
||
import { apiFetch } from "@/app/lib/api-client";
|
||
|
||
interface Service {
|
||
id: string;
|
||
slug: string;
|
||
icon: string;
|
||
title: string;
|
||
titleEn?: string;
|
||
description: string;
|
||
descriptionEn?: string;
|
||
features: string[];
|
||
price: number;
|
||
priceLabel: string;
|
||
category: string;
|
||
gradientFrom: string;
|
||
gradientTo: string;
|
||
}
|
||
|
||
const FALLBACK_SERVICES: Service[] = [
|
||
{
|
||
id: "remote-health-insurance",
|
||
slug: "remote-health-insurance",
|
||
icon: "🛡️",
|
||
title: "远程工作者医疗与旅行保障",
|
||
titleEn: "Remote Health & Travel Coverage",
|
||
description: "覆盖国内旅居、短期出境、设备丢失和紧急医疗咨询。",
|
||
features: ["国内外医疗协助", "设备丢失补偿", "7x24 中文支持", "城市风险提醒"],
|
||
price: 15,
|
||
priceLabel: "¥15/天起",
|
||
category: "保障",
|
||
gradientFrom: "#10b981",
|
||
gradientTo: "#0f766e",
|
||
},
|
||
];
|
||
|
||
export default function ServicesPage() {
|
||
const { t } = useTranslation("services");
|
||
const [services, setServices] = useState<Service[]>(FALLBACK_SERVICES);
|
||
const [activeSlug, setActiveSlug] = useState<string | null>(null);
|
||
const [message, setMessage] = useState("");
|
||
|
||
useEffect(() => {
|
||
apiFetch<{ items: Service[] }>("/api/services")
|
||
.then((data) => {
|
||
if (data.items?.length) {
|
||
setServices(data.items);
|
||
}
|
||
})
|
||
.catch(() => setServices(FALLBACK_SERVICES));
|
||
}, []);
|
||
|
||
const submitLead = async (service: Service) => {
|
||
setActiveSlug(service.slug);
|
||
setMessage("");
|
||
try {
|
||
await apiFetch("/api/services/leads", {
|
||
method: "POST",
|
||
body: JSON.stringify({
|
||
serviceSlug: service.slug,
|
||
note: `用户从服务页咨询:${service.title}`,
|
||
}),
|
||
});
|
||
setMessage(`${service.title} 已记录咨询线索,可在 PocketBase 查看。`);
|
||
} catch (error) {
|
||
setMessage(error instanceof Error ? error.message : "提交失败");
|
||
} finally {
|
||
setActiveSlug(null);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="min-h-screen bg-[#fafafa] dark:bg-gray-950">
|
||
<main className="max-w-[1200px] mx-auto px-4 sm:px-6 py-6 sm:py-8">
|
||
<div className="text-center mb-10 sm:mb-12">
|
||
<h1 className="text-2xl sm:text-3xl md:text-4xl font-bold text-gray-900 dark:text-gray-100 mb-3">
|
||
{t("title")}
|
||
</h1>
|
||
<p className="text-gray-500 dark:text-gray-400 text-sm sm:text-base">
|
||
{t("subtitle")}
|
||
</p>
|
||
</div>
|
||
|
||
{message && (
|
||
<div className="mb-6 rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-800 dark:border-emerald-800/60 dark:bg-emerald-950/30 dark:text-emerald-200">
|
||
{message}
|
||
</div>
|
||
)}
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 sm:gap-6">
|
||
{services.map((service) => (
|
||
<article
|
||
key={service.slug}
|
||
className="bg-white dark:bg-gray-900 rounded-2xl overflow-hidden shadow-sm border border-gray-100 dark:border-gray-800 hover:shadow-lg transition-shadow"
|
||
>
|
||
<div
|
||
className="h-2"
|
||
style={{ background: `linear-gradient(90deg, ${service.gradientFrom}, ${service.gradientTo})` }}
|
||
/>
|
||
<div className="p-5 sm:p-6">
|
||
<div className="flex items-start justify-between mb-4">
|
||
<div className="flex items-center gap-3">
|
||
<span className="text-3xl">{service.icon}</span>
|
||
<div>
|
||
<h3 className="font-bold text-gray-900 dark:text-gray-100 text-lg">
|
||
{service.title}
|
||
</h3>
|
||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||
{service.titleEn || service.category}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4 line-clamp-3">
|
||
{service.description}
|
||
</p>
|
||
|
||
<ul className="space-y-2 mb-5">
|
||
{(service.features || []).slice(0, 5).map((feature) => (
|
||
<li
|
||
key={feature}
|
||
className="flex items-center gap-2 text-xs sm:text-sm text-gray-500 dark:text-gray-400"
|
||
>
|
||
<span className="text-[#ff4d4f]">✓</span>
|
||
{feature}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
|
||
<div className="flex items-center justify-between gap-3 pt-4 border-t border-gray-100 dark:border-gray-800">
|
||
<span className="font-bold text-gray-900 dark:text-gray-100">
|
||
{service.priceLabel || `¥${service.price}`}
|
||
</span>
|
||
<div className="flex items-center gap-2">
|
||
<button
|
||
type="button"
|
||
onClick={() => submitLead(service)}
|
||
disabled={activeSlug === service.slug}
|
||
className="rounded-lg bg-[#ff4d4f] px-3 py-2 text-xs font-medium text-white hover:bg-[#ff3333] disabled:cursor-not-allowed disabled:bg-gray-300"
|
||
>
|
||
{activeSlug === service.slug ? "提交中" : "咨询"}
|
||
</button>
|
||
<Link
|
||
href="/join"
|
||
className="text-sm font-medium text-[#ff4d4f] hover:text-[#ff7a45] transition-colors"
|
||
>
|
||
{t("learnMore")} →
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
))}
|
||
</div>
|
||
|
||
<div className="mt-12 sm:mt-16 bg-gradient-to-r from-[#ff4d4f] to-[#ff7a45] rounded-2xl p-6 sm:p-10 text-center">
|
||
<h2 className="text-xl sm:text-2xl font-bold text-white mb-3">
|
||
需要定制落地方案?
|
||
</h2>
|
||
<p className="text-white/80 mb-5 text-sm sm:text-base">
|
||
服务咨询会写入数据库,后续可按城市、预算、路线和会员状态继续分配。
|
||
</p>
|
||
<Link
|
||
href="/contact"
|
||
className="inline-block bg-white text-[#ff4d4f] px-6 py-2.5 rounded-full font-medium hover:bg-gray-100 transition-colors"
|
||
>
|
||
{t("contactSupport")}
|
||
</Link>
|
||
</div>
|
||
</main>
|
||
|
||
<Footer />
|
||
</div>
|
||
);
|
||
}
|