Files
manoon-headless/src/app/[locale]/solutions/by-oil/page.tsx
Unchained f6609f07d7 feat: implement programmatic SEO solutions hub
- Add /solutions hub page with 10 category cards
- Add /solutions/by-concern directory page
- Add /solutions/by-oil directory page
- Add Solutions section to Footer with navigation links
- Add Breadcrumb component for solution pages
- Add translations for all solution pages (sr, en, de, fr)
- Fix ExitIntentDetector JSON parsing error
- Update sitemap with solution pages
- Create 3 sample solution pages with data files
2026-04-05 05:21:57 +02:00

166 lines
5.6 KiB
TypeScript

import { Metadata } from "next";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import { ChevronRight, Droplets } from "lucide-react";
import { getAllOilForConcernPages, getLocalizedString } from "@/lib/programmatic-seo/dataLoader";
type Params = Promise<{ locale: string }>;
export async function generateMetadata({
params,
}: {
params: Params;
}): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "Solutions.ByOil" });
return {
title: t("metaTitle"),
description: t("metaDescription"),
};
}
function groupByOil(pages: Awaited<ReturnType<typeof getAllOilForConcernPages>>) {
const oils = new Map<string, typeof pages>();
pages.forEach((page) => {
const oilSlug = page.oilSlug;
if (!oils.has(oilSlug)) {
oils.set(oilSlug, []);
}
oils.get(oilSlug)?.push(page);
});
return oils;
}
interface OilCardProps {
oilSlug: string;
oilName: string;
concernCount: number;
topConcerns: string[];
locale: string;
}
function OilCard({ oilSlug, oilName, concernCount, topConcerns, locale }: OilCardProps) {
return (
<div className="border border-[#e5e5e5] rounded-lg p-6 hover:border-black transition-colors group">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 rounded-full bg-amber-100 flex items-center justify-center">
<Droplets className="w-5 h-5 text-amber-700" />
</div>
<h3 className="text-lg font-medium text-[#1a1a1a]">
{oilName}
</h3>
</div>
<p className="text-sm text-[#666666] mb-4">
{concernCount} {concernCount === 1 ? "concern solution" : "concern solutions"} available
</p>
<div className="space-y-2 mb-4">
<p className="text-xs uppercase tracking-wider text-[#999999] font-medium">
Best for:
</p>
{topConcerns.slice(0, 3).map((concernName) => (
<div key={concernName} className="flex items-center gap-2 text-sm text-[#666666]">
<div className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
{concernName}
</div>
))}
</div>
<Link
href={`/${locale}/solutions/by-oil/${oilSlug}`}
className="inline-flex items-center text-sm font-medium text-[#1a1a1a] group-hover:text-black transition-colors"
>
Explore Oil Solutions
<ChevronRight className="ml-1 w-4 h-4 transform group-hover:translate-x-1 transition-transform" />
</Link>
</div>
);
}
export default async function ByOilPage({
params,
}: {
params: Params;
}) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "Solutions" });
const pageT = await getTranslations({ locale, namespace: "Solutions.ByOil" });
const pages = await getAllOilForConcernPages();
const oilsMap = groupByOil(pages);
const oilsList = Array.from(oilsMap.entries())
.map(([slug, pages]) => ({
slug,
name: getLocalizedString(pages[0].oilName, locale),
concernCount: pages.length,
topConcerns: pages.slice(0, 3).map((p) => getLocalizedString(p.concernName, locale)),
}))
.sort((a, b) => a.name.localeCompare(b.name));
return (
<div className="min-h-screen bg-white">
<section className="pt-32 pb-16 lg:pt-40 lg:pb-24">
<div className="container">
<nav className="flex items-center gap-2 text-sm text-[#666666] mb-8">
<Link href={`/${locale}`} className="hover:text-black transition-colors">
{t("breadcrumb.home")}
</Link>
<ChevronRight className="w-4 h-4" />
<Link href={`/${locale}/solutions`} className="hover:text-black transition-colors">
{t("breadcrumb.solutions")}
</Link>
<ChevronRight className="w-4 h-4" />
<span className="text-[#1a1a1a]">{t("breadcrumb.byOil")}</span>
</nav>
<div className="max-w-3xl mb-12">
<h1 className="text-4xl lg:text-5xl font-medium tracking-tight text-[#1a1a1a] mb-6">
{pageT("title")}
</h1>
<p className="text-lg text-[#666666] leading-relaxed">
{pageT("subtitle")}
</p>
</div>
<div className="bg-gradient-to-r from-amber-50 to-emerald-50 border border-[#e5e5e5] rounded-lg p-6 mb-12">
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-full bg-white flex items-center justify-center shadow-sm">
<Droplets className="w-6 h-6 text-amber-600" />
</div>
<div>
<p className="text-sm text-[#666666]">
{pageT("stats.availableOils", { count: oilsList.length })}
</p>
<p className="text-sm text-[#666666]">
{pageT("stats.totalSolutions", { count: pages.length })}
</p>
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{oilsList.map((oil) => (
<OilCard
key={oil.slug}
oilSlug={oil.slug}
oilName={oil.name}
concernCount={oil.concernCount}
topConcerns={oil.topConcerns}
locale={locale}
/>
))}
</div>
{oilsList.length === 0 && (
<div className="text-center py-16">
<p className="text-[#666666]">{pageT("noResults")}</p>
</div>
)}
</div>
</section>
</div>
);
}