diff --git a/src/lib/api/product.ts b/src/lib/api/product.ts index b0380a2..79291ba 100644 --- a/src/lib/api/product.ts +++ b/src/lib/api/product.ts @@ -1,34 +1,219 @@ -export interface Product { - id: string; - name: string; - price: number; - category: string; - description?: string; +export 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?: { + [key: string]: string | number | undefined; + }; + onFavorite?: () => void; + onProductClick?: () => void; + isFavorited?: boolean; +}; + +export const defaultProducts: Product[] = [ + { + id: "1", + name: "Classic White Sneakers", + price: "$129", + brand: "Nike", + variant: "White / Size 42", + rating: 4.5, + reviewCount: "128", + imageSrc: "/placeholders/placeholder3.avif", + imageAlt: "Classic white sneakers", + }, + { + id: "2", + name: "Leather Crossbody Bag", + price: "$89", + brand: "Coach", + variant: "Brown / Medium", + rating: 4.8, + reviewCount: "256", + imageSrc: "/placeholders/placeholder4.webp", + imageAlt: "Brown leather crossbody bag", + }, + { + id: "3", + name: "Wireless Headphones", + price: "$199", + brand: "Sony", + variant: "Black", + rating: 4.7, + reviewCount: "512", + imageSrc: "/placeholders/placeholder3.avif", + imageAlt: "Black wireless headphones", + }, + { + id: "4", + name: "Minimalist Watch", + price: "$249", + brand: "Fossil", + variant: "Silver / 40mm", + rating: 4.6, + reviewCount: "89", + imageSrc: "/placeholders/placeholder4.webp", + imageAlt: "Silver minimalist watch", + }, +]; + +function 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); } -export const fetchProducts = async (): Promise => { - try { - // Simulate API call - const response = await fetch("/api/products"); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return await response.json(); - } catch { - console.error("Failed to fetch products"); - return []; - } -}; +export async function fetchProducts(): Promise { + const apiUrl = process.env.NEXT_PUBLIC_API_URL; + const projectId = process.env.NEXT_PUBLIC_PROJECT_ID; -export const fetchProductById = async (id: string): Promise => { - try { - const response = await fetch(`/api/products/${id}`); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + if (!apiUrl || !projectId) { + return []; } - return await response.json(); - } catch { - console.error("Failed to fetch product"); - return null; - } -}; + + 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: any) => { + const metadata: Record = {}; + if (product.metadata && typeof product.metadata === 'object') { + Object.keys(product.metadata).forEach(key => { + const value = product.metadata[key]; + if (value !== null && value !== undefined) { + const numValue = parseFloat(value); + metadata[key] = isNaN(numValue) ? value : numValue; + } + }); + } + + const imageSrc = product.images?.[0] || product.imageSrc || "/placeholders/placeholder3.avif"; + const imageAlt = product.imageAlt || product.name || ""; + const images = product.images && Array.isArray(product.images) && product.images.length > 0 + ? product.images + : [imageSrc]; + + return { + id: product.id || String(Math.random()), + name: product.name || "Untitled Product", + description: product.description || "", + price: product.default_price?.unit_amount + ? formatPrice(product.default_price.unit_amount, product.default_price.currency || "usd") + : product.price || "$0", + priceId: product.default_price?.id || product.priceId, + imageSrc, + imageAlt, + images, + brand: product.metadata?.brand || product.brand || "", + variant: product.metadata?.variant || product.variant || "", + rating: product.metadata?.rating ? parseFloat(product.metadata.rating) : undefined, + reviewCount: product.metadata?.reviewCount || undefined, + metadata: Object.keys(metadata).length > 0 ? metadata : undefined, + }; + }); + } catch (error) { + return []; + } +} + +export async function fetchProduct(productId: string): Promise { + const apiUrl = process.env.NEXT_PUBLIC_API_URL; + const projectId = process.env.NEXT_PUBLIC_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 = {}; + if (product.metadata && typeof product.metadata === 'object') { + Object.keys(product.metadata).forEach(key => { + const value = product.metadata[key]; + if (value !== null && value !== undefined && value !== '') { + const numValue = parseFloat(String(value)); + metadata[key] = isNaN(numValue) ? String(value) : numValue; + } + }); + } + + let priceValue = product.price; + if (!priceValue && product.default_price?.unit_amount) { + priceValue = formatPrice(product.default_price.unit_amount, product.default_price.currency || "usd"); + } + if (!priceValue) { + priceValue = "$0"; + } + + const imageSrc = product.images?.[0] || product.imageSrc || "/placeholders/placeholder3.avif"; + const imageAlt = product.imageAlt || product.name || ""; + const images = product.images && Array.isArray(product.images) && product.images.length > 0 + ? product.images + : [imageSrc]; + + return { + id: product.id || String(Math.random()), + name: product.name || "Untitled Product", + description: product.description || "", + price: priceValue, + priceId: product.default_price?.id || product.priceId, + imageSrc, + imageAlt, + images, + brand: product.metadata?.brand || product.brand || "", + variant: product.metadata?.variant || product.variant || "", + rating: product.metadata?.rating ? parseFloat(String(product.metadata.rating)) : undefined, + reviewCount: product.metadata?.reviewCount || undefined, + metadata: Object.keys(metadata).length > 0 ? metadata : undefined, + }; + } catch (error) { + return null; + } +} \ No newline at end of file