- Add 10 oil-for-concern solution pages with localized slugs - Support 4 languages: sr, en, de, fr with proper canonical URLs - Add solutions hub, by-concern, and by-oil directory pages - Filter bundle products from solutions pages - Add hideLangSwitcher prop to Header component - Update translations for all languages - Fix canonical URLs to include locale prefix
224 lines
7.8 KiB
TypeScript
224 lines
7.8 KiB
TypeScript
import { Metadata } from "next";
|
|
import Link from "next/link";
|
|
import { getTranslations } from "next-intl/server";
|
|
import { ChevronRight, Droplets, ArrowRight } from "lucide-react";
|
|
import Header from "@/components/layout/Header";
|
|
import Footer from "@/components/layout/Footer";
|
|
import { isValidLocale, DEFAULT_LOCALE } from "@/lib/i18n/locales";
|
|
|
|
type Params = Promise<{ locale: string }>;
|
|
|
|
export async function generateMetadata({
|
|
params,
|
|
}: {
|
|
params: Params;
|
|
}): Promise<Metadata> {
|
|
const { locale } = await params;
|
|
const validLocale = isValidLocale(locale) ? locale : DEFAULT_LOCALE;
|
|
const t = await getTranslations({ locale: validLocale, namespace: "Solutions.Hub" });
|
|
|
|
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://manoonoils.com";
|
|
const localePrefix = validLocale === DEFAULT_LOCALE ? "/sr" : `/${validLocale}`;
|
|
const canonicalUrl = `${baseUrl}${localePrefix}/solutions`;
|
|
|
|
return {
|
|
title: t("metaTitle"),
|
|
description: t("metaDescription"),
|
|
alternates: {
|
|
canonical: canonicalUrl,
|
|
},
|
|
openGraph: {
|
|
url: canonicalUrl,
|
|
},
|
|
};
|
|
}
|
|
|
|
interface CategoryCardProps {
|
|
title: string;
|
|
description: string;
|
|
href: string;
|
|
icon: React.ReactNode;
|
|
priority?: boolean;
|
|
}
|
|
|
|
function CategoryCard({ title, description, href, icon, priority }: CategoryCardProps) {
|
|
return (
|
|
<Link
|
|
href={href}
|
|
className={`group block p-6 lg:p-8 border border-[#e5e5e5] rounded-lg hover:border-black transition-all duration-300 hover:shadow-lg ${
|
|
priority ? "bg-gradient-to-br from-amber-50/50 to-white" : "bg-white"
|
|
}`}
|
|
>
|
|
<div className="flex items-start gap-4">
|
|
<div className={`flex-shrink-0 w-12 h-12 rounded-full flex items-center justify-center ${
|
|
priority ? "bg-amber-100 text-amber-700" : "bg-[#f5f5f5] text-[#666666] group-hover:bg-black group-hover:text-white"
|
|
} transition-colors`}>
|
|
{icon}
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<h3 className="text-lg font-medium text-[#1a1a1a] group-hover:text-black transition-colors">
|
|
{title}
|
|
</h3>
|
|
{priority && (
|
|
<span className="px-2 py-0.5 text-[10px] uppercase tracking-wider font-medium bg-amber-100 text-amber-700 rounded-full">
|
|
Popular
|
|
</span>
|
|
)}
|
|
</div>
|
|
<p className="text-sm text-[#666666] leading-relaxed mb-4">
|
|
{description}
|
|
</p>
|
|
<span className="inline-flex items-center text-sm font-medium text-[#1a1a1a] group-hover:text-black transition-colors">
|
|
{priority ? "Explore Solutions" : "Learn More"}
|
|
<ArrowRight className="ml-1 w-4 h-4 transform group-hover:translate-x-1 transition-transform" />
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
interface QuickLinkProps {
|
|
title: string;
|
|
href: string;
|
|
count?: number;
|
|
}
|
|
|
|
function QuickLink({ title, href, count }: QuickLinkProps) {
|
|
return (
|
|
<Link
|
|
href={href}
|
|
className="flex items-center justify-between p-4 border-b border-[#e5e5e5] hover:bg-[#fafafa] transition-colors group"
|
|
>
|
|
<span className="text-[#1a1a1a] group-hover:text-black transition-colors">
|
|
{title}
|
|
</span>
|
|
<div className="flex items-center gap-2">
|
|
{count !== undefined && (
|
|
<span className="text-xs text-[#999999]">{count} solutions</span>
|
|
)}
|
|
<ChevronRight className="w-4 h-4 text-[#999999] group-hover:text-black transition-colors" />
|
|
</div>
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
export default async function SolutionsHubPage({
|
|
params,
|
|
}: {
|
|
params: Params;
|
|
}) {
|
|
const { locale } = await params;
|
|
const t = await getTranslations({ locale, namespace: "Solutions" });
|
|
const hubT = await getTranslations({ locale, namespace: "Solutions.Hub" });
|
|
|
|
const categories = [
|
|
{
|
|
title: hubT("categories.oilForConcern.title"),
|
|
description: hubT("categories.oilForConcern.description"),
|
|
href: `/${locale}/solutions/by-concern`,
|
|
icon: <Droplets className="w-5 h-5" />,
|
|
priority: true,
|
|
},
|
|
];
|
|
|
|
return (
|
|
<>
|
|
<Header locale={locale} hideLangSwitcher={true} />
|
|
<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" />
|
|
<span className="text-[#1a1a1a]">{t("breadcrumb.solutions")}</span>
|
|
</nav>
|
|
|
|
<div className="max-w-3xl">
|
|
<h1 className="text-4xl lg:text-5xl font-medium tracking-tight text-[#1a1a1a] mb-6">
|
|
{hubT("title")}
|
|
</h1>
|
|
<p className="text-lg text-[#666666] leading-relaxed">
|
|
{hubT("subtitle")}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="pb-16 lg:pb-24">
|
|
<div className="container">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 lg:gap-6">
|
|
{categories.map((category) => (
|
|
<CategoryCard key={category.href} {...category} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="pb-16 lg:pb-24">
|
|
<div className="container">
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12">
|
|
<div className="border border-[#e5e5e5] rounded-lg overflow-hidden">
|
|
<div className="p-6 bg-[#fafafa] border-b border-[#e5e5e5]">
|
|
<h2 className="text-lg font-medium text-[#1a1a1a]">
|
|
{hubT("quickAccess.byConcern")}
|
|
</h2>
|
|
<p className="text-sm text-[#666666] mt-1">
|
|
{hubT("quickAccess.byConcernDesc")}
|
|
</p>
|
|
</div>
|
|
<div className="divide-y divide-[#e5e5e5]">
|
|
<QuickLink
|
|
title={hubT("quickAccess.links.viewAll")}
|
|
href={`/${locale}/solutions/by-concern`}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="border border-[#e5e5e5] rounded-lg overflow-hidden">
|
|
<div className="p-6 bg-[#fafafa] border-b border-[#e5e5e5]">
|
|
<h2 className="text-lg font-medium text-[#1a1a1a]">
|
|
{hubT("quickAccess.byOil")}
|
|
</h2>
|
|
<p className="text-sm text-[#666666] mt-1">
|
|
{hubT("quickAccess.byOilDesc")}
|
|
</p>
|
|
</div>
|
|
<div className="divide-y divide-[#e5e5e5]">
|
|
<QuickLink
|
|
title={hubT("quickAccess.links.viewAll")}
|
|
href={`/${locale}/solutions/by-oil`}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="pb-16 lg:pb-24">
|
|
<div className="container">
|
|
<div className="bg-[#1a1a1a] rounded-2xl p-8 lg:p-12 text-center">
|
|
<h2 className="text-2xl lg:text-3xl font-medium text-white mb-4">
|
|
{hubT("cta.title")}
|
|
</h2>
|
|
<p className="text-[#999999] max-w-xl mx-auto mb-8">
|
|
{hubT("cta.description")}
|
|
</p>
|
|
<Link
|
|
href={`/${locale}/products`}
|
|
className="inline-flex items-center justify-center px-8 py-3 bg-white text-[#1a1a1a] font-medium rounded-full hover:bg-[#f5f5f5] transition-colors"
|
|
>
|
|
{hubT("cta.button")}
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
<Footer locale={locale} />
|
|
</>
|
|
);
|
|
}
|