chore: transfer repo

This commit is contained in:
Danijel
2026-01-19 20:21:14 +01:00
commit 7d2fb0c737
213 changed files with 18085 additions and 0 deletions

View File

@@ -0,0 +1,252 @@
'use client';
import { LanguageSwitcher } from '@/components/i18n/LanguageSwitcher';
import { Button } from '@/components/ui/Button';
import { useTranslation } from '@/lib/hooks/useTranslation';
import { container } from '@/lib/utils';
import { useCart } from 'components/cart/cart-context';
import { Menu, X } from 'lucide-react';
import Image from 'next/image';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useEffect, useState } from 'react';
export default function Navbar() {
const { t, locale } = useTranslation();
const [isMenuOpen, setIsMenuOpen] = useState(false);
const { cart } = useCart();
const pathname = usePathname();
// Create links with translations and proper prefix
const getNavLinks = () => {
const links = [
{ name: t('navbar.build_box'), path: '/build-box' },
{ name: t('navbar.products'), path: '/products' },
{ name: t('navbar.about'), path: '/about' }
];
// Add locale prefix for English paths
if (locale === 'en') {
return links.map(link => ({
...link,
href: `/en${link.path}`
}));
}
// Return paths as is for Croatian
return links.map(link => ({
...link,
href: link.path
}));
};
const navLinks = getNavLinks();
// Close mobile menu on resize to desktop
useEffect(() => {
const handleResize = () => {
if (window.innerWidth >= 768) {
setIsMenuOpen(false);
}
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
// Prevent scrolling when mobile menu is open
useEffect(() => {
if (isMenuOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
return () => {
document.body.style.overflow = '';
};
}, [isMenuOpen]);
// Create home link with proper prefix
const homeLink = locale === 'en' ? '/en' : '/';
// Create cart link with proper prefix
const cartLink = locale === 'en' ? '/en/cart' : '/cart';
return (
<>
<header className="border-b border-gray-200 sticky top-0 z-40 bg-white">
<div className={`${container} flex h-16 items-center justify-between`}>
{/* Logo */}
<Link href={homeLink} className="flex items-center h-12">
<Image
src="/assets/images/logo.svg"
alt="Logo"
width={125}
height={35}
className="w-[125px] h-auto object-contain"
priority
/>
</Link>
{/* Desktop Nav and Cart */}
<div className="hidden md:flex items-center space-x-8">
{navLinks.map((link) => {
const isActive = pathname === link.href ||
(link.href !== homeLink && pathname.startsWith(link.href));
return (
<Link
key={link.name}
href={link.href}
className={`text-sm font-medium transition-colors ${
isActive
? 'text-primary hover:text-primary-dark'
: 'text-black hover:text-primary'
}`}
>
{link.name}
</Link>
);
})}
<LanguageSwitcher />
<Link
href={cartLink}
className="inline-flex h-10 items-center justify-center rounded-button bg-primary px-6 py-2 text-sm font-medium text-white relative hover:bg-primary-dark"
>
<span>{t('navbar.cart')}</span>
<Image
src="/assets/images/cart-icon.png"
alt="Cart"
width={20}
height={20}
className="ml-2"
/>
{cart?.totalQuantity ? (
<span className="absolute -right-2 -top-2 flex h-5 w-5 items-center justify-center rounded-full bg-red-500 text-xs text-white">
{cart.totalQuantity}
</span>
) : null}
</Link>
</div>
{/* Mobile menu button and cart */}
<div className="md:hidden flex items-center space-x-4">
<Link
href={cartLink}
className="relative text-black"
aria-label="View cart"
>
<Image
src="/assets/images/cart-icon-black.png"
alt="Cart"
width={24}
height={24}
/>
{cart?.totalQuantity ? (
<span className="absolute -right-2 -top-3 flex h-5 w-5 items-center justify-center rounded-full bg-red-500 text-xs text-white">
{cart.totalQuantity}
</span>
) : null}
</Link>
<button
className="text-gray-500"
onClick={() => setIsMenuOpen(true)}
aria-label="Open menu"
>
<Menu className="h-6 w-6" />
</button>
</div>
{/* Mobile menu */}
{isMenuOpen && (
<div className="fixed inset-0 z-50 bg-white md:hidden">
{/* Mobile menu header - use same container and height as main navbar */}
<div className={`${container} flex h-16 items-center justify-between`}>
<Link
href={homeLink}
className="flex items-center h-12"
onClick={() => setIsMenuOpen(false)}
>
<Image
src="/assets/images/logo.svg"
alt="Logo"
width={125}
height={35}
className="w-[125px] h-auto object-contain"
priority
/>
</Link>
<button
className="text-gray-500"
onClick={() => setIsMenuOpen(false)}
aria-label="Close menu"
>
<X className="h-6 w-6" />
</button>
</div>
{/* Mobile menu links */}
<div className={`${container} flex-1 flex flex-col py-4`}>
<div className="space-y-4">
{navLinks.map((link) => {
const isActive = pathname === link.href ||
(link.href !== homeLink && pathname.startsWith(link.href));
return (
<Link
key={link.name}
href={link.href}
className={`block text-lg font-medium py-2 transition-colors ${
isActive
? 'text-primary'
: 'text-black hover:text-primary'
}`}
onClick={() => setIsMenuOpen(false)}
>
{link.name}
</Link>
);
})}
<div className="py-2">
<LanguageSwitcher />
</div>
<Link
href={cartLink}
className="mt-4 flex h-12 w-full items-center justify-center relative"
onClick={() => setIsMenuOpen(false)}
>
<Button
variant="primary"
size="lg"
className="w-full h-full flex items-center justify-center"
>
<span>{t('navbar.cart')}</span>
<Image
src="/assets/images/cart-icon.png"
alt="Cart"
width={20}
height={20}
className="ml-2"
/>
{cart?.totalQuantity ? (
<span className="absolute right-4 flex h-5 w-5 items-center justify-center rounded-full bg-red-500 text-xs text-white">
{cart.totalQuantity}
</span>
) : null}
</Button>
</Link>
</div>
</div>
</div>
)}
</div>
</header>
</>
);
}

View File

@@ -0,0 +1,40 @@
'use client';
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import Form from 'next/form';
import { useSearchParams } from 'next/navigation';
export default function Search() {
const searchParams = useSearchParams();
return (
<Form action="/search" className="w-max-[550px] relative w-full lg:w-80 xl:w-full">
<input
key={searchParams?.get('q')}
type="text"
name="q"
placeholder="Search for products..."
autoComplete="off"
defaultValue={searchParams?.get('q') || ''}
className="w-full px-4 py-2 border"
/>
<div className="absolute right-0 top-0 mr-3 flex h-full items-center">
<MagnifyingGlassIcon className="h-4" />
</div>
</Form>
);
}
export function SearchSkeleton() {
return (
<form className="w-max-[550px] relative w-full lg:w-80 xl:w-full">
<input
placeholder="Search for products..."
className="w-full"
/>
<div className="absolute right-0 top-0 mr-3 flex h-full items-center">
<MagnifyingGlassIcon className="h-4" />
</div>
</form>
);
}