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,17 @@
'use client';
import { useTranslation } from "@/lib/hooks/useTranslation";
interface ProductCounterProps {
count: number;
}
export function ProductCounter({ count }: ProductCounterProps) {
const { t } = useTranslation();
return (
<p className="text-sm text-gray-500">
{t('products.counter.showing').replace('{count}', count.toString())}
</p>
);
}

View File

@@ -0,0 +1,75 @@
'use client';
import { useTranslation } from "@/lib/hooks/useTranslation";
import { Product } from "lib/shopify/types";
import { useState } from "react";
import { Heading } from "../../ui/Typography";
import { FilterSidebar } from "../FilterSidebar";
import { FilterButton } from "../ProductComponents/FilterButton";
import { FilterTagList } from "../ProductComponents/FilterTagList";
import { ProductsList } from "../ProductComponents/ProductsList";
import { ProductCounter } from "../client/ProductCounter";
import { SortDropdown } from "../client/SortDropdown";
import { useProductFilters } from "../hooks/useProductFilters";
interface ProductGridWithTranslationProps {
products: Product[];
}
export function ProductGridWithTranslation({ products }: ProductGridWithTranslationProps) {
const { t } = useTranslation();
const [isSidebarOpen, setSidebarOpen] = useState(false);
const {
activeFilters,
sortOption,
sortedProducts,
handleApplyFilters,
handleSortChange,
removeFilter,
clearAllFilters,
formatFilterTag
} = useProductFilters(products);
return (
<div className="w-full">
<div>
<Heading level={2} className="mb-8">{t('products.title')}</Heading>
{/* Filter and Sort Controls */}
<div className="flex justify-between items-center mb-4">
<FilterButton
onClick={() => setSidebarOpen(true)}
filterText={t('products.filters.button')}
/>
<SortDropdown
value={sortOption}
onChange={handleSortChange}
/>
</div>
{/* Filter tags and product count */}
<div className="flex justify-between items-center mb-8">
<FilterTagList
filters={activeFilters}
formatTag={formatFilterTag}
onRemove={removeFilter}
onClearAll={clearAllFilters}
clearAllText={t('products.filters.clearAll')}
/>
<ProductCounter count={sortedProducts.length} />
</div>
{/* Products grid */}
<ProductsList products={sortedProducts} />
</div>
{/* Filter Sidebar */}
<FilterSidebar
isOpen={isSidebarOpen}
onClose={() => setSidebarOpen(false)}
onApplyFilters={handleApplyFilters}
activeFilters={activeFilters}
/>
</div>
);
}

View File

@@ -0,0 +1,31 @@
'use client';
import { Section } from "@/components/ui/Section";
import { Heading, Text } from "@/components/ui/Typography";
import { useTranslation } from "@/lib/hooks/useTranslation";
import { Product } from "lib/shopify/types";
import { ProductGridWithTranslation } from "./ProductGridWithTranslation";
interface ProductsPageContentProps {
products: Product[];
}
export function ProductsPageContent({ products }: ProductsPageContentProps) {
const { t } = useTranslation();
// If no products, display a message
if (!products || products.length === 0) {
return (
<Section>
<Heading level={1} align="center" className="mb-4">{t('products.title')}</Heading>
<Text className="text-center">{t('products.emptyMessage')}</Text>
</Section>
);
}
return (
<Section spacing="xs">
<ProductGridWithTranslation products={products} />
</Section>
);
}

View File

@@ -0,0 +1,117 @@
'use client';
import { useTranslation } from "@/lib/hooks/useTranslation";
import { useEffect, useRef, useState } from "react";
interface SortDropdownProps {
value: string;
onChange: (value: string) => void;
label?: string;
}
export function SortDropdown({ value, onChange, label = "Label" }: SortDropdownProps) {
const { t } = useTranslation();
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
// Get the currently selected option's display text
const getSelectedOptionText = () => {
switch(value) {
case 'newest': return t('products.sort.newest');
case 'price-asc': return t('products.sort.priceAsc');
case 'price-desc': return t('products.sort.priceDesc');
case 'name-asc': return t('products.sort.nameAsc');
case 'name-desc': return t('products.sort.nameDesc');
default: return t('products.sort.title');
}
};
// Handle option selection
const handleSelect = (value: string) => {
onChange(value);
setIsOpen(false);
};
// Close dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
return (
<div className="relative" ref={dropdownRef}>
{/* Label */}
<div className="text-sm text-gray-500 mb-1">{label}</div>
{/* Dropdown button */}
<div
className="flex items-center justify-between border border-gray-300 rounded px-4 py-2 bg-white cursor-pointer"
onClick={() => setIsOpen(!isOpen)}
>
<span className="text-gray-800">{getSelectedOptionText()}</span>
<svg
className={`w-4 h-4 ml-2 transform ${isOpen ? 'rotate-180' : ''}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
clipRule="evenodd"
/>
</svg>
</div>
{/* Dropdown menu */}
{isOpen && (
<div className="absolute z-10 mt-1 w-full bg-white border border-gray-300 rounded shadow-lg">
<div
className={`px-4 py-2 cursor-pointer hover:bg-primary/10 ${value === 'default' ? 'bg-primary/10 text-primary' : 'text-gray-800'}`}
onClick={() => handleSelect('default')}
>
{t('products.sort.title')}
</div>
<div
className={`px-4 py-2 cursor-pointer hover:bg-primary/10 ${value === 'newest' ? 'bg-primary/10 text-primary' : 'text-gray-800'}`}
onClick={() => handleSelect('newest')}
>
{t('products.sort.newest')}
</div>
<div
className={`px-4 py-2 cursor-pointer hover:bg-primary/10 ${value === 'price-asc' ? 'bg-primary/10 text-primary' : 'text-gray-800'}`}
onClick={() => handleSelect('price-asc')}
>
{t('products.sort.priceAsc')}
</div>
<div
className={`px-4 py-2 cursor-pointer hover:bg-primary/10 ${value === 'price-desc' ? 'bg-primary/10 text-primary' : 'text-gray-800'}`}
onClick={() => handleSelect('price-desc')}
>
{t('products.sort.priceDesc')}
</div>
<div
className={`px-4 py-2 cursor-pointer hover:bg-primary/10 ${value === 'name-asc' ? 'bg-primary/10 text-primary' : 'text-gray-800'}`}
onClick={() => handleSelect('name-asc')}
>
{t('products.sort.nameAsc')}
</div>
<div
className={`px-4 py-2 cursor-pointer hover:bg-primary/10 ${value === 'name-desc' ? 'bg-primary/10 text-primary' : 'text-gray-800'}`}
onClick={() => handleSelect('name-desc')}
>
{t('products.sort.nameDesc')}
</div>
</div>
)}
</div>
);
}