ifixkart-storefront/lib/utils.ts

147 lines
4.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
/** Coerce API money fields (number | "199.00" | null) to a finite number. */
export function parseMoney(value: unknown): number {
if (typeof value === 'number') return Number.isFinite(value) ? value : 0;
if (typeof value === 'string') {
const cleaned = value.trim().replace(/,/g, '');
if (!cleaned) return 0;
const n = Number(cleaned);
return Number.isFinite(n) ? n : 0;
}
return 0;
}
export type CatalogPriceInfo = {
price: number;
priceMax?: number;
oldPrice?: number;
discount?: number;
};
/**
* Resolve display price from either full ProductResponse (variants)
* or list ProductCardResponse (top-level price / compare_price).
*/
export function getCatalogPriceInfo(product: {
price?: unknown;
compare_price?: unknown;
discount_percent?: unknown;
variants?: Array<{ price?: unknown; compare_price?: unknown }> | null;
}): CatalogPriceInfo {
const variants = product.variants || [];
const prices = variants.map((v) => parseMoney(v.price)).filter((n) => n > 0);
const compares = variants
.map((v) => parseMoney(v.compare_price))
.filter((n) => n > 0);
let price = prices.length ? Math.min(...prices) : 0;
const priceMax = prices.length > 1 ? Math.max(...prices) : undefined;
let oldPrice = compares.length ? Math.max(...compares) : undefined;
// List/card endpoints expose price on the product, not variants
if (!price) price = parseMoney(product.price);
if (!oldPrice) {
const compare = parseMoney(product.compare_price);
if (compare > 0) oldPrice = compare;
}
let discount: number | undefined;
if (price && oldPrice && oldPrice > price) {
discount = Math.round(((oldPrice - price) / oldPrice) * 100);
} else {
const pct = parseMoney(product.discount_percent);
if (pct > 0) discount = Math.round(pct);
}
return {
price,
priceMax: priceMax && priceMax > price ? priceMax : undefined,
oldPrice: oldPrice && oldPrice > price ? oldPrice : undefined,
discount,
};
}
export function formatCatalogPriceLabel(product: {
price?: unknown;
compare_price?: unknown;
variants?: Array<{ price?: unknown; compare_price?: unknown }> | null;
}): string {
const { price, priceMax } = getCatalogPriceInfo(product);
if (!price) return formatCurrency(0);
if (priceMax && priceMax > price) {
return `${formatCurrency(price)} ${formatCurrency(priceMax)}`;
}
return formatCurrency(price);
}
export function formatCurrency(amount: number | string, currency = 'INR'): string {
return new Intl.NumberFormat('en-IN', {
style: 'currency',
currency: 'INR',
maximumFractionDigits: 2,
}).format(parseMoney(amount));
}
export function getImageUrl(path?: string | null | unknown): string {
let resolvedPath: string;
if (typeof path === 'string') {
resolvedPath = path;
} else if (path && typeof path === 'object') {
const obj = path as Record<string, unknown>;
resolvedPath = String(obj.image_url ?? obj.url ?? obj.src ?? '');
} else {
return '/images/placeholder-product.webp';
}
if (!resolvedPath || resolvedPath === 'null' || resolvedPath === 'undefined') {
return '/images/placeholder-product.webp';
}
const trimmed = resolvedPath.trim();
if (!trimmed) return '/images/placeholder-product.webp';
if (
trimmed.startsWith('http://') ||
trimmed.startsWith('https://') ||
trimmed.startsWith('data:') ||
trimmed.startsWith('blob:')
) {
return trimmed;
}
let cleanPath = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
// Local storefront static assets (e.g. /images/placeholder-product.webp, /icons/...)
if (
cleanPath.startsWith('/images/') ||
cleanPath.startsWith('/icons/') ||
cleanPath.startsWith('/assets/') ||
cleanPath.startsWith('/_next/')
) {
return cleanPath;
}
// Normalize relative upload paths (/uploads/product/..., product/..., uploads/product/...)
if (cleanPath.startsWith('/product/')) {
cleanPath = `/uploads${cleanPath}`;
} else if (!cleanPath.startsWith('/uploads/')) {
const withoutLeadingSlash = cleanPath.replace(/^\//, '');
if (withoutLeadingSlash.startsWith('uploads/')) {
cleanPath = `/${withoutLeadingSlash}`;
} else if (withoutLeadingSlash.startsWith('product/')) {
cleanPath = `/uploads/${withoutLeadingSlash}`;
}
}
const backendUrl =
process.env.NEXT_PUBLIC_API_URL || 'https://ifixkartbe.trionixsolution.com';
return `${backendUrl.replace(/\/$/, '')}${cleanPath}`;
}