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,81 @@
import { Product } from "lib/shopify/types";
/**
* Color mapping from color names to hex codes
*/
export const colorHexMap: Record<string, string> = {
'red': '#FF0000',
'blue': '#0000FF',
'green': '#00FF00',
'black': '#000000',
'white': '#FFFFFF',
'yellow': '#FFFF00',
'purple': '#800080',
'pink': '#FFC0CB',
'orange': '#FFA500',
'gray': '#808080',
'crvena': '#FF0000',
'plava': '#0000FF',
'zelena': '#00FF00',
'crna': '#000000',
'bijela': '#FFFFFF',
'žuta': '#FFFF00',
'ljubičasta': '#800080',
'roza': '#FFC0CB',
'narančasta': '#FFA500',
'siva': '#808080'
};
export type ColorVariant = {
color: string; // Hex code
variantId: string;
price: number;
colorName: string;
}
/**
* Extract color options from a product if they exist
* Returns an array of hex color codes, or undefined if no colors
*/
export function getProductColors(product: Product) {
// Get colors directly from variants
const colorVariants = getProductColorVariants(product);
if (!colorVariants) return undefined;
// Return just the color codes
return colorVariants.map(variant => variant.color);
}
/**
* Get detailed color variants including price and variant ID
* Extracts directly from the variants array
*/
export function getProductColorVariants(product: Product): ColorVariant[] | undefined {
if (!product?.variants || product.variants.length === 0) return undefined;
const result: ColorVariant[] = [];
// Iterate through all variants looking for color options
product.variants.forEach(variant => {
// Find any color option in the selectedOptions
const colorSelectedOption = variant.selectedOptions?.find(
option => option.name.toLowerCase() === 'color' ||
option.name.toLowerCase() === 'colour' ||
option.name.toLowerCase() === 'boja'
);
if (colorSelectedOption) {
const colorName = colorSelectedOption.value;
const colorHex = colorHexMap[colorName.toLowerCase()] || colorName;
result.push({
color: colorHex,
variantId: variant.id,
price: parseFloat(variant.price.amount),
colorName: colorName
});
}
});
return result.length > 0 ? result : undefined;
}

View File

@@ -0,0 +1,177 @@
import { Product } from "lib/shopify/types";
/**
* Sorts products based on the selected option
*/
export function sortProducts(products: Product[], sortOption: string) {
const productsCopy = [...products];
switch (sortOption) {
case 'price-asc':
return productsCopy.sort((a, b) =>
parseFloat(a.priceRange.maxVariantPrice.amount) - parseFloat(b.priceRange.maxVariantPrice.amount)
);
case 'price-desc':
return productsCopy.sort((a, b) =>
parseFloat(b.priceRange.maxVariantPrice.amount) - parseFloat(a.priceRange.maxVariantPrice.amount)
);
case 'newest':
return productsCopy.sort((a, b) => a.handle.localeCompare(b.handle));
case 'name-asc':
return productsCopy.sort((a, b) => a.title.localeCompare(b.title));
case 'name-desc':
return productsCopy.sort((a, b) => b.title.localeCompare(a.title));
default:
return productsCopy;
}
}
/**
* Filters products based on active filters and sorts them
*/
export function filterAndSortProducts(products: Product[], activeFilters: string[], sortOption: string) {
// Filter products based on active filters
const filteredProducts = products.filter(product => {
if (activeFilters.length === 0) return true;
// Check for price filter
const priceFilter = activeFilters.find(filter => filter.startsWith('Cijena:'));
if (priceFilter) {
// Extract min and max prices from filter string (format: "Cijena: 5€ - 100€")
const priceMatch = priceFilter.match(/Cijena: (\d+)€ - (\d+)€/);
if (priceMatch && priceMatch[1] && priceMatch[2]) {
const minPrice = parseInt(priceMatch[1]);
const maxPrice = parseInt(priceMatch[2]);
const productPrice = parseFloat(product.priceRange.maxVariantPrice.amount);
// If product price is outside the filter range, exclude it
if (productPrice < minPrice || productPrice > maxPrice) {
return false;
}
}
}
// For now, we're only implementing price filtering
return true;
});
// Sort filtered products
return sortProducts(filteredProducts, sortOption);
}
// URL parameter constants
const PARAM_FILTERS = 'filters';
const PARAM_MIN_PRICE = 'min_price';
const PARAM_MAX_PRICE = 'max_price';
const PARAM_SORT = 'sort';
const PRICE_PREFIX = 'Cijena:';
/**
* Functions for handling filter tags
*/
export const filterUtils = {
// Format filter tag for display
formatFilterTag: (filter: string) => {
// If it's a price filter, show as is
if (filter.startsWith(PRICE_PREFIX)) {
return filter;
}
// For other filters, split by colon and show in format "Category: Option"
const parts = filter.split(':');
if (parts.length > 1) {
return `${parts[0]}: ${parts[1]}`;
}
// Fallback for any non-formatted filters
return filter;
},
extractPriceFilter: (priceFilter: string) => {
const priceMatch = priceFilter?.match(/Cijena: (\d+)€ - (\d+)€/);
if (priceMatch && priceMatch[1] && priceMatch[2]) {
return {
minPrice: priceMatch[1],
maxPrice: priceMatch[2]
};
}
return null;
},
// Create a price filter string from min and max value
createPriceFilter: (minPrice: string, maxPrice: string) => {
return `${PRICE_PREFIX} ${minPrice}€ - ${maxPrice}`;
},
// Separate price and non-price filters
separateFilters: (filters: string[]) => {
const priceFilter = filters.find(f => f.startsWith(PRICE_PREFIX));
const nonPriceFilters = filters.filter(f => !f.startsWith(PRICE_PREFIX));
return { priceFilter, nonPriceFilters };
},
encodeNonPriceFilters: (nonPriceFilters: string[]) => {
return nonPriceFilters.length > 0
? encodeURIComponent(JSON.stringify(nonPriceFilters))
: '';
},
decodeNonPriceFilters: (encodedFilters: string | null) => {
if (!encodedFilters) return [];
try {
const decodedFilters = JSON.parse(decodeURIComponent(encodedFilters));
return Array.isArray(decodedFilters) ? decodedFilters : [];
} catch (e) {
console.error('Error parsing filters from URL', e);
return [];
}
},
updateUrlParams: (filters: string[], sort: string) => {
const params = new URLSearchParams();
const { priceFilter, nonPriceFilters } = filterUtils.separateFilters(filters);
// Add non-price filters
const encodedFilters = filterUtils.encodeNonPriceFilters(nonPriceFilters);
if (encodedFilters) {
params.set(PARAM_FILTERS, encodedFilters);
}
// Add price filter
if (priceFilter) {
const priceValues = filterUtils.extractPriceFilter(priceFilter);
if (priceValues) {
params.set(PARAM_MIN_PRICE, priceValues.minPrice);
params.set(PARAM_MAX_PRICE, priceValues.maxPrice);
}
}
if (sort !== 'default') {
params.set(PARAM_SORT, sort);
}
const newUrl = params.toString()
? `${window.location.pathname}?${params.toString()}`
: window.location.pathname;
window.history.pushState({}, '', newUrl);
},
getFiltersFromUrl: (searchParams: URLSearchParams) => {
const encodedFilters = searchParams.get(PARAM_FILTERS);
const minPrice = searchParams.get(PARAM_MIN_PRICE);
const maxPrice = searchParams.get(PARAM_MAX_PRICE);
// Start with decoded non-price filters
const nonPriceFilters = filterUtils.decodeNonPriceFilters(encodedFilters);
// Add price filter if present
if (minPrice && maxPrice) {
const priceFilter = filterUtils.createPriceFilter(minPrice, maxPrice);
return [...nonPriceFilters, priceFilter];
}
return nonPriceFilters;
}
}