Files
7cfd7724-27a5-4350-bec7-e9c…/src/lib/api/product.ts
2026-06-14 11:41:11 +00:00

223 lines
7.4 KiB
TypeScript

type Product = {
id: string;
name: string;
price: string;
imageSrc: string;
imageAlt?: string;
images?: string[];
brand?: string;
variant?: string;
rating?: number;
reviewCount?: string;
description?: string;
priceId?: string;
metadata?: Record<string, string | number | undefined>;
onFavorite?: () => void;
onProductClick?: () => void;
isFavorited?: boolean;
};
const defaultProducts: Product[] = [
{
id: "1",
name: "Classic White Sneakers",
price: "$129",
brand: "Nike",
variant: "White / Size 42",
rating: 4.5,
reviewCount: "128",
imageSrc: "https://storage.googleapis.com/webild/default/placeholders/placeholder-1.webp",
imageAlt: "Classic white sneakers",
},
{
id: "2",
name: "Leather Crossbody Bag",
price: "$89",
brand: "Coach",
variant: "Brown / Medium",
rating: 4.8,
reviewCount: "256",
imageSrc: "https://storage.googleapis.com/webild/default/placeholders/placeholder-2.webp",
imageAlt: "Brown leather crossbody bag",
},
{
id: "3",
name: "Wireless Headphones",
price: "$199",
brand: "Sony",
variant: "Black",
rating: 4.7,
reviewCount: "512",
imageSrc: "https://storage.googleapis.com/webild/default/placeholders/placeholder-3.webp",
imageAlt: "Black wireless headphones",
},
{
id: "4",
name: "Minimalist Watch",
price: "$249",
brand: "Fossil",
variant: "Silver / 40mm",
rating: 4.6,
reviewCount: "89",
imageSrc: "https://storage.googleapis.com/webild/default/placeholders/placeholder-1.webp",
imageAlt: "Silver minimalist watch",
},
];
const formatPrice = (amount: number, currency: string): string => {
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: currency.toUpperCase(),
minimumFractionDigits: 0,
maximumFractionDigits: 2,
});
return formatter.format(amount / 100);
};
const fetchProducts = async (): Promise<Product[]> => {
const apiUrl = import.meta.env.VITE_API_URL;
const projectId = import.meta.env.VITE_PROJECT_ID;
if (!apiUrl || !projectId) {
return [];
}
try {
const url = `${apiUrl}/stripe/project/products?projectId=${projectId}&expandDefaultPrice=true`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
return [];
}
const resp = await response.json();
const data = resp.data.data || resp.data;
if (!Array.isArray(data) || data.length === 0) {
return [];
}
return data.map((product: Record<string, unknown>) => {
const metadata: Record<string, string | number | undefined> = {};
const productMetadata = product.metadata as Record<string, unknown> | undefined;
if (productMetadata && typeof productMetadata === "object") {
Object.keys(productMetadata).forEach((key) => {
const value = productMetadata[key];
if (value !== null && value !== undefined) {
const numValue = parseFloat(String(value));
metadata[key] = isNaN(numValue) ? String(value) : numValue;
}
});
}
const images = product.images as string[] | undefined;
const imageSrc = images?.[0] || (product.imageSrc as string) || "https://storage.googleapis.com/webild/default/placeholders/placeholder-1.webp";
const imageAlt = (product.imageAlt as string) || (product.name as string) || "";
const finalImages = images && Array.isArray(images) && images.length > 0 ? images : [imageSrc];
const defaultPrice = product.default_price as Record<string, unknown> | undefined;
return {
id: (product.id as string) || String(Math.random()),
name: (product.name as string) || "Untitled Product",
description: (product.description as string) || "",
price: defaultPrice?.unit_amount
? formatPrice(defaultPrice.unit_amount as number, (defaultPrice.currency as string) || "usd")
: (product.price as string) || "$0",
priceId: (defaultPrice?.id as string) || (product.priceId as string),
imageSrc,
imageAlt,
images: finalImages,
brand: (productMetadata?.brand as string) || (product.brand as string) || "",
variant: (productMetadata?.variant as string) || (product.variant as string) || "",
rating: productMetadata?.rating ? parseFloat(String(productMetadata.rating)) : undefined,
reviewCount: (productMetadata?.reviewCount as string) || undefined,
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
};
});
} catch {
return [];
}
};
const fetchProduct = async (productId: string): Promise<Product | null> => {
const apiUrl = import.meta.env.VITE_API_URL;
const projectId = import.meta.env.VITE_PROJECT_ID;
if (!apiUrl || !projectId) {
return null;
}
try {
const url = `${apiUrl}/stripe/project/products/${productId}?projectId=${projectId}&expandDefaultPrice=true`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
return null;
}
const resp = await response.json();
const product = resp.data?.data || resp.data || resp;
if (!product || typeof product !== "object") {
return null;
}
const metadata: Record<string, string | number | undefined> = {};
const productMetadata = product.metadata as Record<string, unknown> | undefined;
if (productMetadata && typeof productMetadata === "object") {
Object.keys(productMetadata).forEach((key) => {
const value = productMetadata[key];
if (value !== null && value !== undefined && value !== "") {
const numValue = parseFloat(String(value));
metadata[key] = isNaN(numValue) ? String(value) : numValue;
}
});
}
const defaultPrice = product.default_price as Record<string, unknown> | undefined;
let priceValue = product.price as string | undefined;
if (!priceValue && defaultPrice?.unit_amount) {
priceValue = formatPrice(defaultPrice.unit_amount as number, (defaultPrice.currency as string) || "usd");
}
if (!priceValue) {
priceValue = "$0";
}
const images = product.images as string[] | undefined;
const imageSrc = images?.[0] || (product.imageSrc as string) || "https://storage.googleapis.com/webild/default/placeholders/placeholder-1.webp";
const imageAlt = (product.imageAlt as string) || (product.name as string) || "";
const finalImages = images && Array.isArray(images) && images.length > 0 ? images : [imageSrc];
return {
id: (product.id as string) || String(Math.random()),
name: (product.name as string) || "Untitled Product",
description: (product.description as string) || "",
price: priceValue,
priceId: (defaultPrice?.id as string) || (product.priceId as string),
imageSrc,
imageAlt,
images: finalImages,
brand: (productMetadata?.brand as string) || (product.brand as string) || "",
variant: (productMetadata?.variant as string) || (product.variant as string) || "",
rating: productMetadata?.rating ? parseFloat(String(productMetadata.rating)) : undefined,
reviewCount: (productMetadata?.reviewCount as string) || undefined,
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
};
} catch {
return null;
}
};
export { fetchProducts, fetchProduct, defaultProducts, formatPrice, type Product };