chore: transfer repo
This commit is contained in:
81
components/products/utils/colorUtils.ts
Normal file
81
components/products/utils/colorUtils.ts
Normal 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;
|
||||
}
|
||||
177
components/products/utils/productHelpers.ts
Normal file
177
components/products/utils/productHelpers.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user