Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/major-turtles-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@godaddy/localizations": patch
"@godaddy/react": patch
---

- Add filtering by `productIds` and `categoryIds` to ProductGrid
- Add `productId` prop to ProductCard for single product rendering
- Add pagination support to ProductGrid with `enablePagination` prop
- Add translations for pagination controls
6 changes: 5 additions & 1 deletion examples/nextjs/app/store/products.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
'use client';

import { ProductGrid } from '@godaddy/react';
import { ProductGrid, ProductSearch } from '@godaddy/react';
import { useCart } from './layout';

export default function ProductsPage() {
const { openCart } = useCart();

return (
<div className='container mx-auto'>
<div className='mb-6 max-w-md'>
<ProductSearch />
</div>
<ProductGrid
enablePagination
getProductHref={sku => `/store/product/${sku}`}
onAddToCartSuccess={openCart}
/>
Expand Down
2 changes: 1 addition & 1 deletion examples/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@tanstack/react-query": "^5.66.0",
"@tanstack/react-query-devtools": "^5.76.1",
"lucide-react": "^0.475.0",
"next": "16.0.1",
"next": "16.0.7",
"react": "19.2.0",
"react-dom": "19.2.0",
"zod": "^3.24.1"
Expand Down
3 changes: 3 additions & 0 deletions packages/localizations/src/deDe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,5 +384,8 @@ export const deDe = {
remove: 'Entfernen',
removing: 'Wird entfernt...',
checkout: 'Zur Kasse',
itemsPerPage: 'Artikel pro Seite:',
search: 'Suchen',
searchPlaceholder: 'Produkte suchen...',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/enIe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,5 +361,8 @@ export const enIe = {
remove: 'Remove',
removing: 'Removing...',
checkout: 'Checkout',
itemsPerPage: 'Items per page:',
search: 'Search',
searchPlaceholder: 'Search products...',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/enUs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,5 +361,8 @@ export const enUs = {
remove: 'Remove',
removing: 'Removing...',
checkout: 'Checkout',
itemsPerPage: 'Items per page:',
search: 'Search',
searchPlaceholder: 'Search products...',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/esAr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,5 +367,8 @@ export const esAr = {
remove: 'Eliminar',
removing: 'Eliminando...',
checkout: 'Pagar',
itemsPerPage: 'Artículos por página:',
search: 'Buscar',
searchPlaceholder: 'Buscar productos...',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/esCl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,5 +369,8 @@ export const esCl = {
remove: 'Eliminar',
removing: 'Eliminando...',
checkout: 'Pagar',
itemsPerPage: 'Artículos por página:',
search: 'Buscar',
searchPlaceholder: 'Buscar productos...',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/esCo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,5 +367,8 @@ export const esCo = {
remove: 'Eliminar',
removing: 'Eliminando...',
checkout: 'Pagar',
itemsPerPage: 'Artículos por página:',
search: 'Buscar',
searchPlaceholder: 'Buscar productos...',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/esEs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,5 +372,8 @@ export const esEs = {
remove: 'Eliminar',
removing: 'Eliminando...',
checkout: 'Pagar',
itemsPerPage: 'Artículos por página:',
search: 'Buscar',
searchPlaceholder: 'Buscar productos...',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/esMx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,5 +368,8 @@ export const esMx = {
remove: 'Eliminar',
removing: 'Eliminando...',
checkout: 'Pagar',
itemsPerPage: 'Artículos por página:',
search: 'Buscar',
searchPlaceholder: 'Buscar productos...',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/esPe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,5 +367,8 @@ export const esPe = {
remove: 'Eliminar',
removing: 'Eliminando...',
checkout: 'Pagar',
itemsPerPage: 'Artículos por página:',
search: 'Buscar',
searchPlaceholder: 'Buscar productos...',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/esUs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,5 +367,8 @@ export const esUs = {
remove: 'Eliminar',
removing: 'Eliminando...',
checkout: 'Pagar',
itemsPerPage: 'Artículos por página:',
search: 'Buscar',
searchPlaceholder: 'Buscar productos...',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/frCa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,5 +384,8 @@ export const frCa = {
remove: 'Supprimer',
removing: 'Suppression...',
checkout: 'Commander',
itemsPerPage: 'Articles par page :',
search: 'Rechercher',
searchPlaceholder: 'Rechercher des produits...',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/frFr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,5 +385,8 @@ export const frFr = {
remove: 'Supprimer',
removing: 'Suppression...',
checkout: 'Commander',
itemsPerPage: 'Articles par page :',
search: 'Rechercher',
searchPlaceholder: 'Rechercher des produits...',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/idId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,5 +360,8 @@ export const idId = {
remove: 'Hapus',
removing: 'Menghapus...',
checkout: 'Checkout',
itemsPerPage: 'Item per halaman:',
search: 'Cari',
searchPlaceholder: 'Cari produk...',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/itIt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,5 +384,8 @@ export const itIt = {
remove: 'Rimuovi',
removing: 'Rimozione...',
checkout: 'Acquista',
itemsPerPage: 'Articoli per pagina:',
search: 'Cerca',
searchPlaceholder: 'Cerca prodotti...',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/ptBr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,5 +365,8 @@ export const ptBr = {
remove: 'Remover',
removing: 'Removendo...',
checkout: 'Finalizar compra',
itemsPerPage: 'Itens por página:',
search: 'Pesquisar',
searchPlaceholder: 'Pesquisar produtos...',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/qaPs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,5 +369,8 @@ export const qaPs = {
remove: '[Remove]',
removing: '[Removing...]',
checkout: '[Checkout]',
itemsPerPage: '[Items per page:]',
search: '[Search]',
searchPlaceholder: '[Search products...]',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/trTr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,5 +360,8 @@ export const trTr = {
remove: 'Kaldır',
removing: 'Kaldırılıyor...',
checkout: 'Ödeme yap',
itemsPerPage: 'Sayfa başına öğe:',
search: 'Ara',
searchPlaceholder: 'Ürün ara...',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/viVn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,5 +361,8 @@ export const viVn = {
remove: 'Xóa',
removing: 'Đang xóa...',
checkout: 'Thanh toán',
itemsPerPage: 'Mục trên mỗi trang:',
search: 'Tìm kiếm',
searchPlaceholder: 'Tìm kiếm sản phẩm...',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/zhCn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,5 +348,8 @@ export const zhCn = {
remove: '删除',
removing: '正在删除...',
checkout: '结账',
itemsPerPage: '每页项目数:',
search: '搜索',
searchPlaceholder: '搜索产品...',
},
};
3 changes: 3 additions & 0 deletions packages/localizations/src/zhSg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,5 +348,8 @@ export const zhSg = {
remove: '删除',
removing: '正在删除...',
checkout: '结账',
itemsPerPage: '每页项目数:',
search: '搜索',
searchPlaceholder: '搜索产品...',
},
};
6 changes: 5 additions & 1 deletion packages/react/biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
"$schema": "https://biomejs.dev/schemas/2.3.2/schema.json",
"extends": ["biome-config-godaddy/biome.json"],
"files": {
"includes": ["**/*", "!!**/src/globals.css"]
"includes": [
"**/*",
"!!**/src/globals.css",
"!!**/src/globals-tailwind.css"
]
},
"linter": {
"rules": {
Expand Down
2 changes: 1 addition & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"@radix-ui/react-scroll-area": "^1.2.3",
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.1.3",
"@radix-ui/react-toast": "^1.2.6",
"@radix-ui/react-toggle": "^1.1.2",
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/components/storefront/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './cart-totals.tsx';
export * from './product-card';
export * from './product-details.tsx';
export * from './product-grid';
export * from './product-search';
74 changes: 64 additions & 10 deletions packages/react/src/components/storefront/product-card.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,90 @@
'use client';

import { useQuery } from '@tanstack/react-query';
import { ChevronRight, Loader2, ShoppingBag } from 'lucide-react';
import { useFormatCurrency } from '@/components/checkout/utils/format-currency';
import { useAddToCart } from '@/components/storefront/hooks/use-add-to-cart';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import { RouterLink } from '@/components/ui/link';
import { Skeleton } from '@/components/ui/skeleton';
import { useGoDaddyContext } from '@/godaddy-provider';
import { getSkuGroup } from '@/lib/godaddy/godaddy';
import { SKUGroup } from '@/types.ts';

interface ProductCardProps {
product: SKUGroup;
product?: SKUGroup;
productId?: string;
storeId?: string;
clientId?: string;
href?: string;
getProductHref?: (productId: string) => string;
onAddToCartSuccess?: () => void;
onAddToCartError?: (error: Error) => void;
}

export function ProductCard({
product,
href,
product: productProp,
productId,
storeId: storeIdProp,
clientId: clientIdProp,
href: hrefProp,
getProductHref,
onAddToCartSuccess,
onAddToCartError,
}: ProductCardProps) {
// All hooks must be called at the top, before any conditional returns
const context = useGoDaddyContext();
const { t } = context;
const formatCurrency = useFormatCurrency();
const { t } = useGoDaddyContext();
const storeId = storeIdProp || context.storeId;
const clientId = clientIdProp || context.clientId;

// Fetch product by ID if productId is provided
const { data: fetchedProductData, isLoading } = useQuery({
queryKey: ['sku-group', productId, storeId, clientId],
queryFn: () =>
getSkuGroup({ id: productId! }, storeId!, clientId!, context.apiHost),
enabled: !!productId && !!storeId && !!clientId && !productProp,
});

// Use shared add to cart hook
const { addToCart, isLoading: isAddingToCart } = useAddToCart({
onSuccess: onAddToCartSuccess,
onError: onAddToCartError,
});

// Use fetched product or prop product
const product = productProp || fetchedProductData?.skuGroup;

// Compute href with priority: explicit href > getProductHref > no link
const resolvedProductId = product?.id || productId;
const href =
hrefProp ||
(getProductHref && resolvedProductId
? getProductHref(resolvedProductId)
: undefined);

// Show loading skeleton while fetching
if (isLoading || !product) {
return (
<Card className='overflow-hidden border-border flex flex-col h-full'>
<div className='aspect-square overflow-hidden bg-muted'>
<Skeleton className='w-full h-full' />
</div>
<div className='p-4 space-y-2 flex flex-col flex-1'>
<Skeleton className='h-5 w-3/4' />
<Skeleton className='h-4 w-full' />
<Skeleton className='h-4 w-2/3' />
<div className='flex items-center justify-between pt-2 mt-auto'>
<Skeleton className='h-6 w-20' />
<Skeleton className='h-9 w-24' />
</div>
</div>
</Card>
);
}
const title = product?.label || product?.name || t.storefront.product;
const description = product?.description || '';
const priceMin = product?.priceRange?.min || 0;
Expand All @@ -51,12 +111,6 @@ export function ProductCard({
const isFirstSkuInStock = availableInventory > 0;
const hasMultipleSkus = (product?.skus?.edges?.length || 0) > 1;

// Use shared add to cart hook
const { addToCart, isLoading: isAddingToCart } = useAddToCart({
onSuccess: onAddToCartSuccess,
onError: onAddToCartError,
});

const handleAddToCart = async (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
Expand Down
Loading