Update src/lib/api/product.ts

This commit is contained in:
2026-03-12 03:55:23 +00:00
parent 636e81ccdf
commit d9d8db3367

View File

@@ -1,219 +1,145 @@
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;
'use client';
interface Product {
id: string;
name: string;
price: number;
description: string;
category: string;
rating: number;
imageSrc: string;
}
interface ApiResponse<T> {
success: boolean;
data?: T;
message?: string;
}
// Fetch all products
export const fetchProducts = async (): Promise<ApiResponse<Product[]>> => {
try {
const response = await fetch('/api/products');
if (!response.ok) {
throw new Error('Failed to fetch products');
}
const data = await response.json();
return { success: true, data };
} catch {
return {
success: false,
message: 'Failed to fetch products',
};
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: "https://webuild-dev.s3.eu-north-1.amazonaws.com/default/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: "https://webuild-dev.s3.eu-north-1.amazonaws.com/default/placeholder4.webp",
imageAlt: "Brown leather crossbody bag",
},
{
id: "3",
name: "Wireless Headphones",
price: "$199",
brand: "Sony",
variant: "Black",
rating: 4.7,
reviewCount: "512",
imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/default/placeholder3.avif",
imageAlt: "Black wireless headphones",
},
{
id: "4",
name: "Minimalist Watch",
price: "$249",
brand: "Fossil",
variant: "Silver / 40mm",
rating: 4.6,
reviewCount: "89",
imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/default/placeholder4.webp",
imageAlt: "Silver minimalist watch",
},
];
// Fetch a single product by ID
export const fetchProductById = async (id: string): Promise<ApiResponse<Product>> => {
try {
const response = await fetch(`/api/products/${id}`);
if (!response.ok) {
throw new Error('Failed to fetch product');
}
const data = await response.json();
return { success: true, data };
} catch {
return {
success: false,
message: 'Failed to fetch product',
};
}
};
function formatPrice(amount: number, currency: string): string {
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: currency.toUpperCase(),
minimumFractionDigits: 0,
maximumFractionDigits: 2,
// Search products
export const searchProducts = async (query: string): Promise<ApiResponse<Product[]>> => {
try {
const response = await fetch(`/api/products/search?q=${encodeURIComponent(query)}`);
if (!response.ok) {
throw new Error('Failed to search products');
}
const data = await response.json();
return { success: true, data };
} catch {
return {
success: false,
message: 'Failed to search products',
};
}
};
// Filter products by category
export const filterProductsByCategory = async (category: string): Promise<ApiResponse<Product[]>> => {
try {
const response = await fetch(`/api/products/category/${encodeURIComponent(category)}`);
if (!response.ok) {
throw new Error('Failed to fetch products');
}
const data = await response.json();
return { success: true, data };
} catch {
return {
success: false,
message: 'Failed to fetch products',
};
}
};
// Create a new product (admin only)
export const createProduct = async (product: Omit<Product, 'id'>): Promise<ApiResponse<Product>> => {
try {
const response = await fetch('/api/products', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(product),
});
return formatter.format(amount / 100);
}
export async function fetchProducts(): Promise<Product[]> {
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
const projectId = process.env.NEXT_PUBLIC_PROJECT_ID;
if (!apiUrl || !projectId) {
return [];
if (!response.ok) {
throw new Error('Failed to create product');
}
const data = await response.json();
return { success: true, data };
} catch {
return {
success: false,
message: 'Failed to create product',
};
}
};
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<string, string | number | undefined> = {};
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 || "https://webuild-dev.s3.eu-north-1.amazonaws.com/default/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 [];
// Update a product (admin only)
export const updateProduct = async (id: string, updates: Partial<Product>): Promise<ApiResponse<Product>> => {
try {
const response = await fetch(`/api/products/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates),
});
if (!response.ok) {
throw new Error('Failed to update product');
}
}
const data = await response.json();
return { success: true, data };
} catch {
return {
success: false,
message: 'Failed to update product',
};
}
};
export async function fetchProduct(productId: string): Promise<Product | null> {
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
const projectId = process.env.NEXT_PUBLIC_PROJECT_ID;
if (!apiUrl || !projectId) {
return null;
// Delete a product (admin only)
export const deleteProduct = async (id: string): Promise<ApiResponse<null>> => {
try {
const response = await fetch(`/api/products/${id}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error('Failed to delete product');
}
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> = {};
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 || "https://webuild-dev.s3.eu-north-1.amazonaws.com/default/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;
}
}
return { success: true, data: null };
} catch {
return {
success: false,
message: 'Failed to delete product',
};
}
};