feat: implement programmatic SEO for solutions pages
- 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
This commit is contained in:
@@ -14,9 +14,10 @@ import type { Locale } from "@/lib/i18n/locales";
|
||||
|
||||
interface HeaderProps {
|
||||
locale?: string;
|
||||
hideLangSwitcher?: boolean;
|
||||
}
|
||||
|
||||
export default function Header({ locale: propLocale = "sr" }: HeaderProps) {
|
||||
export default function Header({ locale: propLocale = "sr", hideLangSwitcher = false }: HeaderProps) {
|
||||
const t = useTranslations("Header");
|
||||
const pathname = usePathname();
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
@@ -132,39 +133,41 @@ export default function Header({ locale: propLocale = "sr" }: HeaderProps) {
|
||||
</Link>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<div ref={dropdownRef} className="relative">
|
||||
<button
|
||||
className="p-2 hover:bg-black/5 rounded-full transition-colors flex items-center gap-1"
|
||||
onClick={() => setLangDropdownOpen(!langDropdownOpen)}
|
||||
aria-label="Select language"
|
||||
>
|
||||
<Globe className="w-5 h-5" strokeWidth={1.5} />
|
||||
<span className="text-sm">{currentLocale.flag}</span>
|
||||
</button>
|
||||
<AnimatePresence>
|
||||
{langDropdownOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
className="absolute right-0 top-full mt-1 bg-white border border-[#e5e5e5] shadow-lg rounded-md overflow-hidden z-50"
|
||||
>
|
||||
{SUPPORTED_LOCALES.map((loc) => (
|
||||
<button
|
||||
key={loc}
|
||||
onClick={() => switchLocale(loc)}
|
||||
className={`flex items-center gap-2 px-4 py-2 text-sm hover:bg-black/5 transition-colors w-full text-left ${
|
||||
loc === locale ? "bg-black/5 font-medium" : ""
|
||||
}`}
|
||||
>
|
||||
<span>{LOCALE_CONFIG[loc].flag}</span>
|
||||
<span>{LOCALE_CONFIG[loc].label}</span>
|
||||
</button>
|
||||
))}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
{!hideLangSwitcher && (
|
||||
<div ref={dropdownRef} className="relative">
|
||||
<button
|
||||
className="p-2 hover:bg-black/5 rounded-full transition-colors flex items-center gap-1"
|
||||
onClick={() => setLangDropdownOpen(!langDropdownOpen)}
|
||||
aria-label="Select language"
|
||||
>
|
||||
<Globe className="w-5 h-5" strokeWidth={1.5} />
|
||||
<span className="text-sm">{currentLocale.flag}</span>
|
||||
</button>
|
||||
<AnimatePresence>
|
||||
{langDropdownOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
className="absolute right-0 top-full mt-1 bg-white border border-[#e5e5e5] shadow-lg rounded-md overflow-hidden z-50"
|
||||
>
|
||||
{SUPPORTED_LOCALES.map((loc) => (
|
||||
<button
|
||||
key={loc}
|
||||
onClick={() => switchLocale(loc)}
|
||||
className={`flex items-center gap-2 px-4 py-2 text-sm hover:bg-black/5 transition-colors w-full text-left ${
|
||||
loc === locale ? "bg-black/5 font-medium" : ""
|
||||
}`}
|
||||
>
|
||||
<span>{LOCALE_CONFIG[loc].flag}</span>
|
||||
<span>{LOCALE_CONFIG[loc].label}</span>
|
||||
</button>
|
||||
))}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
className="p-2 hover:bg-black/5 rounded-full transition-colors hidden sm:block"
|
||||
|
||||
Reference in New Issue
Block a user