Files
shopify-template/hooks/use-shopify-products.ts
T
Rami BitarandClaude Opus 5 107959a4c3 Add AI store assistant with catalogue tool calls
- /api/chat streams via AI SDK v7 through OpenRouter (OPENROUTER_API_KEY),
  with a store-specific system prompt and five read-only tools:
  searchCatalogue, getProductDetails, listCollections,
  getCollectionProducts, browseProducts
- Sidebar assistant on the right that opens from a launcher and expands,
  built on ai-elements (conversation, message, prompt-input, tool) with
  shimmer on in-flight tool calls
- Extract services/shopify/catalog.ts so the Storefront fetchers have no
  React imports and can run in a Route Handler; the client hooks now
  re-export from it
- ai-elements pulled in the canonical shadcn primitives, replacing the
  hand-rolled command/dialog/button variants; search dialog moved to the
  cmdk-based Command and CommandDialog forwards shouldFilter
- Pin shiki to ^3.19.0 to match streamdown and drop the duplicate copy
- Add .env.example documenting the required env vars

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jWbNNJLksC1QG8z8845FX
2026-08-01 13:03:01 -04:00

135 lines
3.6 KiB
TypeScript

'use client';
import { useState, useEffect, useCallback } from 'react';
import {
getProducts,
getProduct,
getProductRecommendations,
} from '@/services/shopify/catalog';
// Pure fetchers live in services/shopify/catalog so server code can use them
// too; re-exported here so existing imports keep working.
export {
getProducts,
getProductsPage,
getProduct,
getProductRecommendations,
} from '@/services/shopify/catalog';
export type {
Product,
ProductOption,
ProductOptionValue,
ProductsPage,
} from '@/services/shopify/catalog';
import type { Product } from '@/services/shopify/catalog';
interface UseProductsOptions {
first?: number;
after?: string | null;
query?: string;
sortKey?: 'BEST_SELLING' | 'CREATED_AT' | 'PRICE' | 'TITLE';
reverse?: boolean;
}
interface UseProductsReturn {
products: Product[];
loading: boolean;
error: string | null;
refetch: () => Promise<void>;
}
// Hook for fetching multiple products
export function useProducts(options: UseProductsOptions = {}): UseProductsReturn {
const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchProducts = useCallback(async () => {
try {
setLoading(true);
setError(null);
const data = await getProducts(options);
setProducts(data);
} catch (err) {
console.error('Error fetching products:', err);
setError(err instanceof Error ? err.message : 'Failed to load products');
} finally {
setLoading(false);
}
}, [options.first, options.query, options.sortKey, options.reverse]);
useEffect(() => {
fetchProducts();
}, [fetchProducts]);
return { products, loading, error, refetch: fetchProducts };
}
// Hook for fetching a single product
export function useProduct(handle: string | null) {
const [product, setProduct] = useState<Product | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchProduct = useCallback(async () => {
if (!handle) {
setLoading(false);
return;
}
try {
setLoading(true);
setError(null);
const data = await getProduct(handle);
setProduct(data);
if (!data) {
setError('Product not found');
}
} catch (err) {
console.error('Error fetching product:', err);
setError(err instanceof Error ? err.message : 'Failed to load product');
} finally {
setLoading(false);
}
}, [handle]);
useEffect(() => {
fetchProduct();
}, [fetchProduct]);
return { product, loading, error, refetch: fetchProduct };
}
// Hook for fetching product recommendations
export function useProductRecommendations(productId: string | null) {
const [recommendations, setRecommendations] = useState<Product[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchRecommendations = useCallback(async () => {
if (!productId) {
setLoading(false);
return;
}
try {
setLoading(true);
setError(null);
const data = await getProductRecommendations(productId);
setRecommendations(data);
} catch (err) {
console.error('Error fetching recommendations:', err);
setError(err instanceof Error ? err.message : 'Failed to load recommendations');
} finally {
setLoading(false);
}
}, [productId]);
useEffect(() => {
fetchRecommendations();
}, [fetchRecommendations]);
return { recommendations, loading, error, refetch: fetchRecommendations };
}