From 2a19dcfece07880194a2a166f2ba640e9c1dad6a Mon Sep 17 00:00:00 2001 From: Vula Builder Date: Wed, 29 Jul 2026 18:15:19 +0000 Subject: [PATCH] Deploy --- src/hooks/useProducts.ts | 73 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/hooks/useProducts.ts diff --git a/src/hooks/useProducts.ts b/src/hooks/useProducts.ts new file mode 100644 index 0000000..a21a947 --- /dev/null +++ b/src/hooks/useProducts.ts @@ -0,0 +1,73 @@ +'use client' +import { useState, useEffect } from 'react' + +export interface VulaProductCategory { + id: string + name: string + handle: string +} + +export interface VulaProduct { + id: string + title: string + description: string | null + thumbnail: string | null + created_at?: string + collection?: { id: string; title: string } + variants: Array<{ + id: string + title: string + prices: Array<{ amount: number; currency_code: string }> + }> + categories?: VulaProductCategory[] +} + +interface UseProductsResult { + products: VulaProduct[] + loading: boolean + isLoading: boolean // alias for loading — React Query convention, both work + error: string | null +} + +type UseProductsOptions = number | { limit?: number; categoryId?: string } + +// Accepts either a plain number or an options object — both call signatures are valid: +// useProducts(4) +// useProducts({ limit: 4, categoryId: 'cat_123' }) +export function useProducts(options: UseProductsOptions = 12): UseProductsResult { + // Extract scalars so useEffect deps are stable primitives, not object references. + const limit = typeof options === 'number' ? options : (options.limit ?? 12) + const categoryId = typeof options === 'number' ? undefined : options.categoryId + + const [products, setProducts] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + let controller = new AbortController() + + const fetchProducts = () => { + controller = new AbortController() + const params = new URLSearchParams({ limit: String(limit) }) + if (categoryId) params.set('categoryId', categoryId) + fetch(`/api/products?${params}`, { signal: controller.signal }) + .then(res => res.ok ? res.json() : Promise.reject(res.status)) + .then(data => { + setProducts(data.products ?? []) + setLoading(false) + }) + .catch(err => { if (err?.name !== 'AbortError') { setError(String(err)); setLoading(false) } }) + } + + fetchProducts() + // Poll every 30 seconds so newly published products appear without a manual refresh. + const interval = setInterval(fetchProducts, 30000) + + return () => { + clearInterval(interval) + controller.abort() + } + }, [limit, categoryId]) + + return { products, loading, isLoading: loading, error } +}