{variant === 'shop' ? (
-
- {t('home')}
-
+ <>
+
+ {t('home')}
+
+ {links.map(link => (
+
+ {'labelKey' in link ? t(link.labelKey) : link.label}
+
+ ))}
+ >
) : null}
{variant === 'blog' ? (
-
- ) : (
- links.map(link => (
-
+
+ {t('home')}
+
+ {blogCategories.map(category => {
+ const slug = slugify(category.title || '');
+ const href = `/blog/category/${slug}`;
+ const isActive = pathname === href;
+ return (
+
+ {category.title}
+
+ );
+ })}
+ >
+ ) : null}
+
+ {variant === 'platform' ? (
+ <>
+ {links
+ .filter(link => link.href !== '/shop')
+ .map(link => (
+
+ {'labelKey' in link ? t(link.labelKey) : link.label}
+
+ ))}
+
+
- {'labelKey' in link ? t(link.labelKey) : link.label}
-
- ))
- )}
+ {t('shop')}
+
+ >
+ ) : null}
{variant === 'shop' && showAdminLink ? (
New product
@@ -122,7 +216,7 @@ export function AppMobileMenu({
{t('dashboard')}
@@ -132,25 +226,21 @@ export function AppMobileMenu({
href="/shop/admin"
aria-label="Shop admin"
title="Shop admin"
- className="inline-flex items-center rounded-md px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground"
+ onClick={close}
+ className={linkClass(pathname === '/shop/admin')}
>
Admin
) : null}
- {/* LogoutButton стилізується сам; ми тільки позиціонуємо як пункт меню */}
-
+
>
) : (
-
+
{t('login')}
-
+
)}
diff --git a/frontend/components/header/DesktopActions.tsx b/frontend/components/header/DesktopActions.tsx
new file mode 100644
index 00000000..d0e627f2
--- /dev/null
+++ b/frontend/components/header/DesktopActions.tsx
@@ -0,0 +1,64 @@
+'use client';
+
+import { useTranslations } from 'next-intl';
+import { LogIn, Settings, User } from 'lucide-react';
+
+import { HeaderButton } from '@/components/shared/HeaderButton';
+import { GitHubStarButton } from '@/components/shared/GitHubStarButton';
+import LanguageSwitcher from '@/components/shared/LanguageSwitcher';
+import { LogoutButton } from '@/components/auth/logoutButton';
+import { CartButton } from '@/components/shop/header/cart-button';
+import { BlogHeaderSearch } from '@/components/blog/BlogHeaderSearch';
+
+type DesktopActionsProps = {
+ variant: 'platform' | 'shop' | 'blog';
+ userExists: boolean;
+ showAdminLink?: boolean;
+};
+
+export function DesktopActions({
+ variant,
+ userExists,
+ showAdminLink = false,
+}: DesktopActionsProps) {
+ const t = useTranslations('navigation');
+ const isShop = variant === 'shop';
+ const isBlog = variant === 'blog';
+
+ return (
+
+ {userExists && (
+
+ )}
+
+ {showAdminLink && (
+
+ )}
+
+ {isBlog && }
+
+
+
+
+ {isShop && }
+
+ {!userExists ? (
+
+ {t('login')}
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/frontend/components/header/DesktopNav.tsx b/frontend/components/header/DesktopNav.tsx
new file mode 100644
index 00000000..957fc6ef
--- /dev/null
+++ b/frontend/components/header/DesktopNav.tsx
@@ -0,0 +1,48 @@
+'use client';
+
+import { useTranslations } from 'next-intl';
+import { ShoppingBag } from 'lucide-react';
+import { SITE_LINKS } from '@/lib/navigation';
+
+import { NavLink } from '@/components/header/NavLink';
+import { HeaderButton } from '@/components/shared/HeaderButton';
+import { NavLinks } from '@/components/shop/header/nav-links';
+import { BlogCategoryLinks } from '@/components/blog/BlogCategoryLinks';
+
+type Category = {
+ _id: string;
+ title: string;
+};
+
+type DesktopNavProps = {
+ variant: 'platform' | 'shop' | 'blog';
+ blogCategories?: Category[];
+};
+
+export function DesktopNav({ variant, blogCategories = [] }: DesktopNavProps) {
+ const t = useTranslations('navigation');
+
+ if (variant === 'shop') {
+ return
;
+ }
+
+ if (variant === 'blog') {
+ return
;
+ }
+
+ return (
+
+
+ {SITE_LINKS.filter(link => link.href !== '/shop').map(link => (
+
+ {t(link.labelKey)}
+
+ ))}
+
+
+
+ {t('shop')}
+
+
+ );
+}
diff --git a/frontend/components/header/MainSwitcher.tsx b/frontend/components/header/MainSwitcher.tsx
index 0bdd0df4..52dd41d5 100644
--- a/frontend/components/header/MainSwitcher.tsx
+++ b/frontend/components/header/MainSwitcher.tsx
@@ -19,6 +19,14 @@ function isQaPath(pathname: string): boolean {
return segments[0] === 'q&a' || segments[1] === 'q&a';
}
+function isHomePath(pathname: string): boolean {
+ const segments = pathname.split('/').filter(Boolean);
+ return (
+ segments.length === 0 ||
+ (segments.length === 1 && ['en', 'pl', 'uk'].includes(segments[0]))
+ );
+}
+
type MainSwitcherProps = {
children: ReactNode;
userExists: boolean;
@@ -34,6 +42,7 @@ export function MainSwitcher({
}: MainSwitcherProps) {
const pathname = usePathname();
const isQa = isQaPath(pathname);
+ const isHome = isHomePath(pathname);
if (isShopPath(pathname)) return <>{children}>;
@@ -52,7 +61,11 @@ export function MainSwitcher({
}
return (
-
+
{children}
);
diff --git a/frontend/components/header/MobileActions.tsx b/frontend/components/header/MobileActions.tsx
new file mode 100644
index 00000000..76cf49c9
--- /dev/null
+++ b/frontend/components/header/MobileActions.tsx
@@ -0,0 +1,42 @@
+'use client';
+
+import LanguageSwitcher from '@/components/shared/LanguageSwitcher';
+import { CartButton } from '@/components/shop/header/cart-button';
+import { BlogHeaderSearch } from '@/components/blog/BlogHeaderSearch';
+import { AppMobileMenu } from '@/components/header/AppMobileMenu';
+
+type Category = {
+ _id: string;
+ title: string;
+};
+
+type MobileActionsProps = {
+ variant: 'platform' | 'shop' | 'blog';
+ userExists: boolean;
+ showAdminLink?: boolean;
+ blogCategories?: Category[];
+};
+
+export function MobileActions({
+ variant,
+ userExists,
+ showAdminLink = false,
+ blogCategories = [],
+}: MobileActionsProps) {
+ const isShop = variant === 'shop';
+ const isBlog = variant === 'blog';
+
+ return (
+
+ {isBlog &&
}
+
+ {isShop &&
}
+
+
+ );
+}
diff --git a/frontend/components/header/NavLink.tsx b/frontend/components/header/NavLink.tsx
new file mode 100644
index 00000000..9d66f983
--- /dev/null
+++ b/frontend/components/header/NavLink.tsx
@@ -0,0 +1,32 @@
+'use client';
+
+import { usePathname } from 'next/navigation';
+import { AnimatedNavLink } from '@/components/shared/AnimatedNavLink';
+
+interface NavLinkProps {
+ href: string;
+ children: React.ReactNode;
+ className?: string;
+}
+
+export function NavLink({ href, children, className = '' }: NavLinkProps) {
+ const pathname = usePathname();
+
+ const isActive = (() => {
+ const cleanPathname = pathname.replace(/^\/(uk|en|pl)(?=\/|$)/, '') || '/';
+
+ if (href === '/' && cleanPathname === '/') return true;
+
+ if (href !== '/') {
+ return cleanPathname === href || cleanPathname.startsWith(`${href}/`);
+ }
+
+ return false;
+ })();
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/frontend/components/header/UnifiedHeader.tsx b/frontend/components/header/UnifiedHeader.tsx
index b952b34a..4b5d85aa 100644
--- a/frontend/components/header/UnifiedHeader.tsx
+++ b/frontend/components/header/UnifiedHeader.tsx
@@ -1,17 +1,9 @@
'use client';
-import { LogIn, Settings, User } from 'lucide-react';
-import { useTranslations } from 'next-intl';
-import { Link } from '@/i18n/routing';
-import { SITE_LINKS } from '@/lib/navigation';
-import LanguageSwitcher from '@/components/shared/LanguageSwitcher';
-import { LogoutButton } from '@/components/auth/logoutButton';
-
-import { CartButton } from '@/components/shop/header/cart-button';
-import { NavLinks } from '@/components/shop/header/nav-links';
-import { BlogCategoryLinks } from '@/components/blog/BlogCategoryLinks';
-import { BlogHeaderSearch } from '@/components/blog/BlogHeaderSearch';
-import { AppMobileMenu } from '@/components/header/AppMobileMenu';
+import { Logo } from '@/components/shared/Logo';
+import { DesktopNav } from '@/components/header/DesktopNav';
+import { DesktopActions } from '@/components/header/DesktopActions';
+import { MobileActions } from '@/components/header/MobileActions';
export type UnifiedHeaderVariant = 'platform' | 'shop' | 'blog';
@@ -19,7 +11,6 @@ export type UnifiedHeaderProps = {
variant: UnifiedHeaderVariant;
userExists: boolean;
showAdminLink?: boolean;
- enableSearch?: boolean;
blogCategories?: Array<{ _id: string; title: string }>;
};
@@ -29,105 +20,34 @@ export function UnifiedHeader({
showAdminLink = false,
blogCategories = [],
}: UnifiedHeaderProps) {
- const t = useTranslations('navigation');
- const isShop = variant === 'shop';
- const isBlog = variant === 'blog';
- const brandHref = isShop ? '/shop' : isBlog ? '/blog' : '/';
- const brandBadge = isShop ? t('shop') : isBlog ? t('blog') : '';
+ const brandHref =
+ variant === 'shop' ? '/shop' : variant === 'blog' ? '/blog' : '/';
return (