Deploy
This commit is contained in:
@@ -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<VulaProduct[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(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 }
|
||||
}
|
||||
Reference in New Issue
Block a user