From 86c7c79e4f108930a22fc5ebaffcc7dc62ebba54 Mon Sep 17 00:00:00 2001 From: Phil Bennett Date: Thu, 13 Nov 2025 10:31:35 -0600 Subject: [PATCH 01/13] add cart mutations --- .../app/store/product/[productId]/product.tsx | 8 +- examples/nextjs/app/store/products.tsx | 8 +- .../react/src/components/storefront/cart.tsx | 143 +++++++-- .../storefront/hooks/use-add-to-cart.ts | 120 +++++++ .../components/storefront/product-card.tsx | 51 ++- .../components/storefront/product-details.tsx | 46 ++- .../components/storefront/product-grid.tsx | 14 +- packages/react/src/godaddy-provider.tsx | 4 + packages/react/src/lib/cart-storage.ts | 78 +++++ packages/react/src/lib/godaddy/godaddy.ts | 149 +++++++++ .../godaddy/orders-storefront-mutations.ts | 302 ++++++++++++++++++ .../lib/godaddy/orders-storefront-queries.ts | 169 ++++++++++ packages/react/src/types.ts | 74 +++++ 13 files changed, 1119 insertions(+), 47 deletions(-) create mode 100644 packages/react/src/components/storefront/hooks/use-add-to-cart.ts create mode 100644 packages/react/src/lib/cart-storage.ts create mode 100644 packages/react/src/lib/godaddy/orders-storefront-mutations.ts create mode 100644 packages/react/src/lib/godaddy/orders-storefront-queries.ts diff --git a/examples/nextjs/app/store/product/[productId]/product.tsx b/examples/nextjs/app/store/product/[productId]/product.tsx index bce327b8..9aa51c71 100644 --- a/examples/nextjs/app/store/product/[productId]/product.tsx +++ b/examples/nextjs/app/store/product/[productId]/product.tsx @@ -3,8 +3,11 @@ import { ProductDetails } from '@godaddy/react'; import { ArrowLeft } from 'lucide-react'; import Link from 'next/link'; +import { useCart } from '../../layout'; export default function Product({ productId }: { productId: string }) { + const { openCart } = useCart(); + return (
Back to Store - +
); } diff --git a/examples/nextjs/app/store/products.tsx b/examples/nextjs/app/store/products.tsx index 324a4b06..48a01459 100644 --- a/examples/nextjs/app/store/products.tsx +++ b/examples/nextjs/app/store/products.tsx @@ -1,11 +1,17 @@ 'use client'; import { ProductGrid } from '@godaddy/react'; +import { useCart } from './layout'; export default function ProductsPage() { + const { openCart } = useCart(); + return (
- `/store/product/${sku}`} /> + `/store/product/${sku}`} + onAddToCartSuccess={openCart} + />
); } diff --git a/packages/react/src/components/storefront/cart.tsx b/packages/react/src/components/storefront/cart.tsx index 58a8943e..e125d4ca 100644 --- a/packages/react/src/components/storefront/cart.tsx +++ b/packages/react/src/components/storefront/cart.tsx @@ -1,8 +1,11 @@ 'use client'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { Loader2, ShoppingCart } from 'lucide-react'; import type { Product } from '@/components/checkout/line-items/line-items'; import { CartLineItems } from '@/components/storefront/cart-line-items'; import { CartTotals } from '@/components/storefront/cart-totals'; +import { Button } from '@/components/ui/button'; import { Sheet, SheetContent, @@ -10,6 +13,8 @@ import { SheetTitle, } from '@/components/ui/sheet'; import { useGoDaddyContext } from '@/godaddy-provider'; +import { getCartOrderId } from '@/lib/cart-storage'; +import { deleteCartLineItem, getCartOrder } from '@/lib/godaddy/godaddy'; interface CartProps { open: boolean; @@ -17,39 +22,75 @@ interface CartProps { } export function Cart({ open, onOpenChange }: CartProps) { - // Mock data - const items: Product[] = [ - { - id: 'LineItem_2y0l7o6Oi4BW6fpSiKPX1hhBccU', - name: 'Box of cookies', - image: - 'https://isteam.dev-wsimg.com/ip/2f2e05ec-de6f-4a89-90f2-038c749655b0/cookies.webp', - quantity: 2, - originalPrice: 10.99, - price: 10.99, - notes: [], - }, - { - id: 'LineItem_2y0l9FykA04qp2pC6y3YZ0TbZFD', - name: 'Cupcakes', - image: - 'https://isteam.dev-wsimg.com/ip/2f2e05ec-de6f-4a89-90f2-038c749655b0/cupcakes.webp/:/rs=w:600,h:600', - quantity: 1, - originalPrice: 5.99, - price: 5.99, - notes: [], + const context = useGoDaddyContext(); + const queryClient = useQueryClient(); + const cartOrderId = getCartOrderId(); + + // Fetch cart order + const { + data: cartData, + isLoading, + error, + } = useQuery({ + queryKey: ['cart-order', cartOrderId], + queryFn: () => + getCartOrder( + cartOrderId!, + context.storeId!, + context.clientId!, + context?.apiHost + ), + enabled: !!cartOrderId && !!context.storeId && !!context.clientId, + }); + + // Delete line item mutation + const _deleteMutation = useMutation({ + mutationFn: (lineItemId: string) => + deleteCartLineItem( + { id: lineItemId, orderId: cartOrderId! }, + context.storeId!, + context.clientId!, + context?.apiHost + ), + onSuccess: () => { + // Invalidate cart query to refetch + queryClient.invalidateQueries({ queryKey: ['cart-order', cartOrderId] }); }, - ]; + }); + + const order = cartData?.orderById; + + // Transform cart line items to Product format for CartLineItems component + const items: Product[] = + order?.lineItems?.map(item => ({ + id: item.id, + name: item.name || 'Product', + image: item.details?.productAssetUrl || '', + quantity: item.quantity || 0, + originalPrice: + (item.totals?.subTotal?.value || 0) / 100 / (item.quantity || 1), + price: (item.totals?.subTotal?.value || 0) / 100 / (item.quantity || 1), + notes: item.notes?.map(note => note.content || '') || [], + })) || []; + + // Calculate totals + const itemCount = items.reduce((sum, item) => sum + item.quantity, 0); + const currencyCode = order?.totals?.total?.currencyCode || 'USD'; + const subtotal = (order?.totals?.subTotal?.value || 0) / 100; + const shipping = (order?.totals?.shippingTotal?.value || 0) / 100; + const taxes = (order?.totals?.taxTotal?.value || 0) / 100; + const discount = (order?.totals?.discountTotal?.value || 0) / 100; + const total = (order?.totals?.total?.value || 0) / 100; const totals = { - subtotal: 27.97, - discount: 0, - shipping: 0, - currencyCode: 'USD', - itemCount: 3, - total: 27.97, + subtotal, + discount, + shipping, + currencyCode, + itemCount, + total, tip: 0, - taxes: 0, + taxes, enableDiscounts: false, enableTaxes: true, isTaxLoading: false, @@ -62,8 +103,48 @@ export function Cart({ open, onOpenChange }: CartProps) { Shopping Cart
- - + {isLoading && ( +
+ +
+ )} + + {!isLoading && error && ( +
+

+ Failed to load cart: {(error as Error).message} +

+ +
+ )} + + {!isLoading && !error && (!cartOrderId || items.length === 0) && ( +
+ +

+ Your cart is empty +

+

+ Add items to get started +

+
+ )} + + {!isLoading && !error && cartOrderId && items.length > 0 && ( + <> + + + + )}
diff --git a/packages/react/src/components/storefront/hooks/use-add-to-cart.ts b/packages/react/src/components/storefront/hooks/use-add-to-cart.ts new file mode 100644 index 00000000..34c9d01c --- /dev/null +++ b/packages/react/src/components/storefront/hooks/use-add-to-cart.ts @@ -0,0 +1,120 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useGoDaddyContext } from '@/godaddy-provider'; +import { getCartOrderId, setCartOrderId } from '@/lib/cart-storage'; +import { addCartLineItem, createCartOrder } from '@/lib/godaddy/godaddy'; + +export interface AddToCartInput { + skuId: string; + name: string; + quantity: number; + productAssetUrl?: string; +} + +export interface UseAddToCartOptions { + onSuccess?: () => void; + onError?: (error: Error) => void; +} + +export function useAddToCart(options?: UseAddToCartOptions) { + const context = useGoDaddyContext(); + const queryClient = useQueryClient(); + + // Create cart order mutation + const createCartMutation = useMutation({ + mutationFn: () => + createCartOrder( + { + context: { + storeId: context?.storeId || '', + channelId: context?.channelId || '', + }, + totals: { + subTotal: { value: 0, currencyCode: 'USD' }, + shippingTotal: { value: 0, currencyCode: 'USD' }, + discountTotal: { value: 0, currencyCode: 'USD' }, + feeTotal: { value: 0, currencyCode: 'USD' }, + taxTotal: { value: 0, currencyCode: 'USD' }, + total: { value: 0, currencyCode: 'USD' }, + }, + }, + context.storeId!, + context.clientId!, + context?.apiHost + ), + onSuccess: data => { + if (data.addDraftOrder?.id) { + setCartOrderId(data.addDraftOrder.id); + } + }, + }); + + // Add line item mutation + const addLineItemMutation = useMutation({ + mutationFn: ({ + orderId, + input, + }: { + orderId: string; + input: AddToCartInput; + }) => + addCartLineItem( + { + orderId, + skuId: input.skuId, + name: input.name, + quantity: input.quantity, + fulfillmentMode: 'SHIP', + status: 'DRAFT', + details: { + productAssetUrl: input.productAssetUrl || undefined, + }, + }, + context.storeId!, + context.clientId!, + context?.apiHost + ), + onSuccess: () => { + // Invalidate cart query to refresh + const cartOrderId = getCartOrderId(); + queryClient.invalidateQueries({ queryKey: ['cart-order', cartOrderId] }); + + // Call success callback + options?.onSuccess?.(); + }, + onError: error => { + // Call error callback + options?.onError?.(error as Error); + }, + }); + + const addToCart = async (input: AddToCartInput) => { + if (!context.storeId || !context.clientId) { + const error = new Error('Store ID and Client ID are required'); + options?.onError?.(error); + throw error; + } + let cartOrderId = getCartOrderId(); + + // Create cart if it doesn't exist + if (!cartOrderId) { + const result = await createCartMutation.mutateAsync(); + cartOrderId = result.addDraftOrder?.id || null; + + if (!cartOrderId) { + const error = new Error('Failed to create cart'); + options?.onError?.(error); + throw error; + } + } + + // Add line item to cart + await addLineItemMutation.mutateAsync({ orderId: cartOrderId, input }); + }; + + return { + addToCart, + isLoading: createCartMutation.isPending || addLineItemMutation.isPending, + isCreatingCart: createCartMutation.isPending, + isAddingItem: addLineItemMutation.isPending, + }; +} diff --git a/packages/react/src/components/storefront/product-card.tsx b/packages/react/src/components/storefront/product-card.tsx index 676a5406..a0b42751 100644 --- a/packages/react/src/components/storefront/product-card.tsx +++ b/packages/react/src/components/storefront/product-card.tsx @@ -1,6 +1,7 @@ 'use client'; -import { ChevronRight, ShoppingBag } from 'lucide-react'; +import { ChevronRight, Loader2, ShoppingBag } from 'lucide-react'; +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'; @@ -11,9 +12,16 @@ import { SKUGroup } from '@/types.ts'; interface ProductCardProps { product: SKUGroup; href?: string; + onAddToCartSuccess?: () => void; + onAddToCartError?: (error: Error) => void; } -export function ProductCard({ product, href }: ProductCardProps) { +export function ProductCard({ + product, + href, + onAddToCartSuccess, + onAddToCartError, +}: ProductCardProps) { const title = product?.label || product?.name || 'Product'; const description = product?.description || ''; const priceMin = product?.priceRange?.min || 0; @@ -27,8 +35,30 @@ export function ProductCard({ product, href }: ProductCardProps) { edge => edge?.node?.type === 'IMAGE' )?.node?.url; - const handleAddToCart = (e: React.MouseEvent) => { + // Get first SKU for products without options + const firstSku = product?.skus?.edges?.[0]?.node; + const skuId = firstSku?.code || product?.id || ''; + + // 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(); + + if (!skuId) { + return; + } + + await addToCart({ + skuId, + name: title, + quantity: 1, + productAssetUrl: imageUrl || undefined, + }); }; const cardContent = ( @@ -70,9 +100,18 @@ export function ProductCard({ product, href }: ProductCardProps) { ) : ( - )} diff --git a/packages/react/src/components/storefront/product-details.tsx b/packages/react/src/components/storefront/product-details.tsx index fe50faf5..a6bdcd29 100644 --- a/packages/react/src/components/storefront/product-details.tsx +++ b/packages/react/src/components/storefront/product-details.tsx @@ -1,8 +1,9 @@ 'use client'; import { useQuery } from '@tanstack/react-query'; -import { Minus, Plus, ShoppingCart } from 'lucide-react'; +import { Loader2, Minus, Plus, ShoppingCart } from 'lucide-react'; import { useCallback, useEffect, useMemo, useState } from 'react'; +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'; @@ -24,6 +25,8 @@ interface ProductDetailsProps { productId: string; storeId?: string; clientId?: string; + onAddToCartSuccess?: () => void; + onAddToCartError?: (error: Error) => void; } // Flattened attribute structure for UI (transforms edges/node to flat array) @@ -119,6 +122,8 @@ export function ProductDetails({ productId, storeId: storeIdProp, clientId: clientIdProp, + onAddToCartSuccess, + onAddToCartError, }: ProductDetailsProps) { const context = useGoDaddyContext(); @@ -144,6 +149,15 @@ export function ProductDetails({ return result; }); + // Use shared add to cart hook + const { addToCart, isLoading: isAddingToCart } = useAddToCart({ + onSuccess: () => { + setQuantity(1); // Reset quantity + onAddToCartSuccess?.(); + }, + onError: onAddToCartError, + }); + // Update URL when variant params change const setVariantParams = useCallback( (updates: Record) => { @@ -377,8 +391,17 @@ export function ProductDetails({ const canAddToCart = !isOutOfStock && (!attributes.length || selectedSku); - const handleAddToCart = () => { - // Placeholder for add to cart functionality + const handleAddToCart = async () => { + if (!canAddToCart) { + return; + } + + await addToCart({ + skuId: selectedSku?.code || productId, + name: title, + quantity, + productAssetUrl: images[0] || undefined, + }); }; return ( @@ -500,7 +523,7 @@ export function ProductDetails({ {/* Product Information */} -
+

{title}

@@ -623,10 +646,19 @@ export function ProductDetails({ size='lg' className='w-full gap-2' onClick={handleAddToCart} - disabled={!canAddToCart} + disabled={!canAddToCart || isAddingToCart} > - - {isOutOfStock ? 'Out of Stock' : 'Add to Cart'} + {isAddingToCart ? ( + <> + + Adding to Cart... + + ) : ( + <> + + {isOutOfStock ? 'Out of Stock' : 'Add to Cart'} + + )} {/* Additional Product Information */} diff --git a/packages/react/src/components/storefront/product-grid.tsx b/packages/react/src/components/storefront/product-grid.tsx index dc0fe042..8b0c44f5 100644 --- a/packages/react/src/components/storefront/product-grid.tsx +++ b/packages/react/src/components/storefront/product-grid.tsx @@ -11,6 +11,8 @@ interface ProductGridProps { clientId?: string; first?: number; getProductHref?: (productId: string) => string; + onAddToCartSuccess?: () => void; + onAddToCartError?: (error: Error) => void; } function ProductGridSkeleton({ count = 6 }: { count?: number }) { @@ -34,6 +36,8 @@ export function ProductGrid({ clientId: clientIdProp, first = 100, getProductHref, + onAddToCartSuccess, + onAddToCartError, }: ProductGridProps) { const context = useGoDaddyContext(); const storeId = storeIdProp || context.storeId; @@ -63,7 +67,15 @@ export function ProductGrid({ if (!group?.id) return null; const href = getProductHref?.(group.id); - return ; + return ( + + ); })}
); diff --git a/packages/react/src/godaddy-provider.tsx b/packages/react/src/godaddy-provider.tsx index f39e3bed..4e230a5c 100644 --- a/packages/react/src/godaddy-provider.tsx +++ b/packages/react/src/godaddy-provider.tsx @@ -73,6 +73,7 @@ interface GoDaddyContextValue { apiHost?: string; clientId?: string; storeId?: string; + channelId?: string; Link?: React.ComponentType; } @@ -106,6 +107,7 @@ export interface GoDaddyProviderProps { apiHost?: string; clientId?: string; storeId?: string; + channelId?: string; queryClient?: QueryClient; Link?: React.ComponentType; children: QueryClientProviderProps['children']; @@ -118,6 +120,7 @@ export function GoDaddyProvider({ apiHost, clientId, storeId, + channelId, queryClient: providedQueryClient, Link, children, @@ -211,6 +214,7 @@ export function GoDaddyProvider({ apiHost, clientId, storeId, + channelId, Link, }} > diff --git a/packages/react/src/lib/cart-storage.ts b/packages/react/src/lib/cart-storage.ts new file mode 100644 index 00000000..3f4c2e2b --- /dev/null +++ b/packages/react/src/lib/cart-storage.ts @@ -0,0 +1,78 @@ +const CART_ORDER_ID_KEY = 'godaddy_cart_order_id'; +const CART_CREATED_AT_KEY = 'godaddy_cart_created_at'; +const CART_TTL = 30 * 24 * 60 * 60 * 1000; // 30 days in milliseconds + +/** + * Get the cart order ID from localStorage. + * Returns null if the cart doesn't exist or has expired. + */ +export function getCartOrderId(): string | null { + if (typeof window === 'undefined') { + // SSR safety + return null; + } + + const orderId = localStorage.getItem(CART_ORDER_ID_KEY); + const createdAt = localStorage.getItem(CART_CREATED_AT_KEY); + + // No cart exists + if (!orderId) { + return null; + } + + // Check if cart has expired (30 days old) + if (createdAt) { + const age = Date.now() - parseInt(createdAt, 10); + if (age > CART_TTL) { + // Cart expired, clean it up + clearCartOrderId(); + return null; + } + } + + return orderId; +} + +/** + * Save the cart order ID to localStorage with a timestamp. + */ +export function setCartOrderId(orderId: string): void { + if (typeof window === 'undefined') { + // SSR safety + return; + } + + localStorage.setItem(CART_ORDER_ID_KEY, orderId); + localStorage.setItem(CART_CREATED_AT_KEY, Date.now().toString()); +} + +/** + * Remove the cart order ID and timestamp from localStorage. + */ +export function clearCartOrderId(): void { + if (typeof window === 'undefined') { + // SSR safety + return; + } + + localStorage.removeItem(CART_ORDER_ID_KEY); + localStorage.removeItem(CART_CREATED_AT_KEY); +} + +/** + * Get the age of the current cart in days. + * Returns null if no cart exists. + */ +export function getCartAge(): number | null { + if (typeof window === 'undefined') { + return null; + } + + const createdAt = localStorage.getItem(CART_CREATED_AT_KEY); + if (!createdAt) { + return null; + } + + const ageMs = Date.now() - parseInt(createdAt, 10); + return Math.floor(ageMs / (24 * 60 * 60 * 1000)); // Convert to days +} diff --git a/packages/react/src/lib/godaddy/godaddy.ts b/packages/react/src/lib/godaddy/godaddy.ts index ece3f3ab..8eb40479 100644 --- a/packages/react/src/lib/godaddy/godaddy.ts +++ b/packages/react/src/lib/godaddy/godaddy.ts @@ -6,15 +6,28 @@ import { SkuGroupsQuery, SkuQuery, } from '@/lib/godaddy/catalog-storefront-queries.ts'; +import { + AddCartOrderMutation, + AddLineItemBySkuIdMutation, + ApplyDiscountCodesMutation, + DeleteLineItemByIdMutation, + UpdateCartOrderMutation, + UpdateLineItemByIdMutation, +} from '@/lib/godaddy/orders-storefront-mutations.ts'; +import { GetCartOrderQuery } from '@/lib/godaddy/orders-storefront-queries.ts'; import { graphqlRequestWithErrors } from '@/lib/graphql-with-errors'; import type { + AddCartOrderInput, + AddLineItemBySkuIdInput, ApplyCheckoutSessionDeliveryMethodInput, ApplyCheckoutSessionDiscountInput, ApplyCheckoutSessionFulfillmentLocationInput, ApplyCheckoutSessionShippingMethodInput, + ApplyDiscountCodesInput, CheckoutSession, CheckoutSessionInput, ConfirmCheckoutMutationInput, + DeleteLineItemByIdInput, DraftOrderPriceAdjustmentsQueryInput, GetCheckoutSessionShippingRatesInput, GetCheckoutSessionTaxesInput, @@ -22,7 +35,9 @@ import type { SkuGroupInput, SkuGroupsInput, SkuInput, + UpdateCartOrderInput, UpdateDraftOrderInput, + UpdateLineItemByIdInput, } from '@/types'; import { ApplyCheckoutSessionDeliveryMethodMutation, @@ -1116,3 +1131,137 @@ export function getSku( } ); } + +// Cart/Orders functions +export function createCartOrder( + input: AddCartOrderInput, + storeId: string, + clientId: string, + apiHost?: string +) { + const GODADDY_HOST = getHostByEnvironment(apiHost, 'orders'); + + return graphqlRequestWithErrors>( + `${GODADDY_HOST}/v1/commerce/order-storefront-subgraph`, + AddCartOrderMutation, + { input }, + { + 'X-Store-ID': storeId, + 'X-Client-ID': clientId, + } + ); +} + +export function getCartOrder( + orderId: string, + storeId: string, + clientId: string, + apiHost?: string +) { + const GODADDY_HOST = getHostByEnvironment(apiHost, 'orders'); + + return graphqlRequestWithErrors>( + `${GODADDY_HOST}/v1/commerce/order-storefront-subgraph`, + GetCartOrderQuery, + { id: orderId }, + { + 'X-Store-ID': storeId, + 'X-Client-ID': clientId, + } + ); +} + +export function addCartLineItem( + input: AddLineItemBySkuIdInput, + storeId: string, + clientId: string, + apiHost?: string +) { + const GODADDY_HOST = getHostByEnvironment(apiHost, 'orders'); + + return graphqlRequestWithErrors>( + `${GODADDY_HOST}/v1/commerce/order-storefront-subgraph`, + AddLineItemBySkuIdMutation, + { input }, + { + 'X-Store-ID': storeId, + 'X-Client-ID': clientId, + } + ); +} + +export function updateCartOrder( + input: UpdateCartOrderInput, + storeId: string, + clientId: string, + apiHost?: string +) { + const GODADDY_HOST = getHostByEnvironment(apiHost, 'orders'); + + return graphqlRequestWithErrors>( + `${GODADDY_HOST}/v1/commerce/order-storefront-subgraph`, + UpdateCartOrderMutation, + { input }, + { + 'X-Store-ID': storeId, + 'X-Client-ID': clientId, + } + ); +} + +export function deleteCartLineItem( + input: DeleteLineItemByIdInput, + storeId: string, + clientId: string, + apiHost?: string +) { + const GODADDY_HOST = getHostByEnvironment(apiHost, 'orders'); + + return graphqlRequestWithErrors>( + `${GODADDY_HOST}/v1/commerce/order-storefront-subgraph`, + DeleteLineItemByIdMutation, + input, + { + 'X-Store-ID': storeId, + 'X-Client-ID': clientId, + } + ); +} + +export function applyCartDiscountCodes( + input: ApplyDiscountCodesInput, + storeId: string, + clientId: string, + apiHost?: string +) { + const GODADDY_HOST = getHostByEnvironment(apiHost, 'orders'); + + return graphqlRequestWithErrors>( + `${GODADDY_HOST}/v1/commerce/order-storefront-subgraph`, + ApplyDiscountCodesMutation, + { input }, + { + 'X-Store-ID': storeId, + 'X-Client-ID': clientId, + } + ); +} + +export function updateCartLineItem( + input: UpdateLineItemByIdInput, + storeId: string, + clientId: string, + apiHost?: string +) { + const GODADDY_HOST = getHostByEnvironment(apiHost, 'orders'); + + return graphqlRequestWithErrors>( + `${GODADDY_HOST}/v1/commerce/order-storefront-subgraph`, + UpdateLineItemByIdMutation, + { input }, + { + 'X-Store-ID': storeId, + 'X-Client-ID': clientId, + } + ); +} diff --git a/packages/react/src/lib/godaddy/orders-storefront-mutations.ts b/packages/react/src/lib/godaddy/orders-storefront-mutations.ts new file mode 100644 index 00000000..f485ce0f --- /dev/null +++ b/packages/react/src/lib/godaddy/orders-storefront-mutations.ts @@ -0,0 +1,302 @@ +import { graphql } from '@/lib/gql/gql-orders-storefront.tada'; + +export const AddCartOrderMutation = graphql(` + mutation AddCartOrder($input: AddDraftOrderInput!) { + addDraftOrder(input: $input) { + id + customerId + createdAt + updatedAt + context { + storeId + channelId + } + lineItems { + id + name + quantity + skuId + type + fulfillmentMode + totals { + subTotal { + value + currencyCode + } + taxTotal { + value + currencyCode + } + discountTotal { + value + currencyCode + } + feeTotal { + value + currencyCode + } + } + } + totals { + subTotal { + value + currencyCode + } + shippingTotal { + value + currencyCode + } + taxTotal { + value + currencyCode + } + discountTotal { + value + currencyCode + } + feeTotal { + value + currencyCode + } + total { + value + currencyCode + } + } + } + } +`); + +export const AddLineItemBySkuIdMutation = graphql(` + mutation AddLineItemBySkuId($input: AddLineItemInput!) { + addLineItemBySkuId(input: $input) { + id + name + quantity + skuId + type + fulfillmentMode + details { + productAssetUrl + sku + unitOfMeasure + selectedOptions { + attribute + values + } + selectedAddons { + attribute + sku + values { + name + costAdjustment { + value + currencyCode + } + } + } + } + totals { + subTotal { + value + currencyCode + } + taxTotal { + value + currencyCode + } + discountTotal { + value + currencyCode + } + feeTotal { + value + currencyCode + } + } + discounts { + id + name + code + amount { + value + currencyCode + } + } + taxes { + id + name + amount { + value + currencyCode + } + ratePercentage + } + createdAt + updatedAt + } + } +`); + +export const UpdateCartOrderMutation = graphql(` + mutation UpdateCartOrder($input: UpdateDraftOrderInput!) { + updateDraftOrder(input: $input) { + id + customerId + createdAt + updatedAt + context { + storeId + channelId + } + lineItems { + id + name + quantity + skuId + type + fulfillmentMode + totals { + subTotal { + value + currencyCode + } + taxTotal { + value + currencyCode + } + discountTotal { + value + currencyCode + } + feeTotal { + value + currencyCode + } + } + } + totals { + subTotal { + value + currencyCode + } + shippingTotal { + value + currencyCode + } + taxTotal { + value + currencyCode + } + discountTotal { + value + currencyCode + } + feeTotal { + value + currencyCode + } + total { + value + currencyCode + } + } + } + } +`); + +export const DeleteLineItemByIdMutation = graphql(` + mutation DeleteLineItemById($id: ID!, $orderId: ID!) { + deleteLineItemById(id: $id, orderId: $orderId) + } +`); + +export const ApplyDiscountCodesMutation = graphql(` + mutation ApplyDiscountCodes($input: ApplyDiscountCodesInput!) { + applyDiscountCodes(input: $input) { + id + discounts { + id + name + code + amount { + value + currencyCode + } + ratePercentage + appliedBeforeTax + } + totals { + subTotal { + value + currencyCode + } + shippingTotal { + value + currencyCode + } + taxTotal { + value + currencyCode + } + discountTotal { + value + currencyCode + } + productDiscountTotal { + value + currencyCode + } + shippingDiscountTotal { + value + currencyCode + } + feeTotal { + value + currencyCode + } + total { + value + currencyCode + } + } + } + } +`); + +export const UpdateLineItemByIdMutation = graphql(` + mutation UpdateLineItemById($input: UpdateLineItemByIdInput!) { + updateLineItemById(input: $input) { + id + name + quantity + skuId + type + fulfillmentMode + details { + productAssetUrl + sku + unitOfMeasure + } + totals { + subTotal { + value + currencyCode + } + taxTotal { + value + currencyCode + } + discountTotal { + value + currencyCode + } + feeTotal { + value + currencyCode + } + } + updatedAt + } + } +`); diff --git a/packages/react/src/lib/godaddy/orders-storefront-queries.ts b/packages/react/src/lib/godaddy/orders-storefront-queries.ts new file mode 100644 index 00000000..4a4dcf4c --- /dev/null +++ b/packages/react/src/lib/godaddy/orders-storefront-queries.ts @@ -0,0 +1,169 @@ +import { graphql } from '@/lib/gql/gql-orders-storefront.tada'; + +export const GetCartOrderQuery = graphql(` + query GetCartOrder($id: ID!) { + orderById(id: $id) { + id + customerId + createdAt + updatedAt + context { + storeId + channelId + } + lineItems { + id + name + quantity + skuId + type + fulfillmentMode + details { + productAssetUrl + sku + unitOfMeasure + selectedOptions { + attribute + values + } + selectedAddons { + attribute + sku + values { + name + costAdjustment { + value + currencyCode + } + } + } + } + totals { + subTotal { + value + currencyCode + } + taxTotal { + value + currencyCode + } + discountTotal { + value + currencyCode + } + feeTotal { + value + currencyCode + } + } + discounts { + id + name + code + amount { + value + currencyCode + } + ratePercentage + } + taxes { + id + name + amount { + value + currencyCode + } + ratePercentage + } + notes { + id + content + author + authorType + } + } + totals { + subTotal { + value + currencyCode + } + shippingTotal { + value + currencyCode + } + taxTotal { + value + currencyCode + } + discountTotal { + value + currencyCode + } + productDiscountTotal { + value + currencyCode + } + shippingDiscountTotal { + value + currencyCode + } + feeTotal { + value + currencyCode + } + total { + value + currencyCode + } + } + discounts { + id + name + code + amount { + value + currencyCode + } + ratePercentage + appliedBeforeTax + } + taxes { + id + name + amount { + value + currencyCode + } + ratePercentage + included + exempted + } + shipping { + firstName + lastName + email + phone + companyName + address { + addressLine1 + addressLine2 + addressLine3 + adminArea1 + adminArea2 + adminArea3 + adminArea4 + postalCode + countryCode + } + } + notes { + id + content + author + authorType + createdAt + } + tags + } + } +`); diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index 7f78bda9..d4b06da6 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -23,6 +23,15 @@ import { DraftOrderSkusQuery, DraftOrderTaxesQuery, } from '@/lib/godaddy/checkout-queries.ts'; +import { + AddCartOrderMutation, + AddLineItemBySkuIdMutation, + ApplyDiscountCodesMutation, + DeleteLineItemByIdMutation, + UpdateCartOrderMutation, + UpdateLineItemByIdMutation, +} from '@/lib/godaddy/orders-storefront-mutations.ts'; +import { GetCartOrderQuery } from '@/lib/godaddy/orders-storefront-queries.ts'; export const PaymentProvider = { STRIPE: 'stripe', @@ -270,3 +279,68 @@ export type SKUAttributeValue = NonNullable< >['edges'] >[number] >['node']; + +// Cart/Orders types +export type AddCartOrderInput = VariablesOf< + typeof AddCartOrderMutation +>['input']; + +export type AddLineItemBySkuIdInput = VariablesOf< + typeof AddLineItemBySkuIdMutation +>['input']; + +export type UpdateCartOrderInput = VariablesOf< + typeof UpdateCartOrderMutation +>['input']; + +export type UpdateLineItemByIdInput = VariablesOf< + typeof UpdateLineItemByIdMutation +>['input']; + +export type DeleteLineItemByIdInput = VariablesOf< + typeof DeleteLineItemByIdMutation +>; + +export type ApplyDiscountCodesInput = VariablesOf< + typeof ApplyDiscountCodesMutation +>['input']; + +export type CartOrder = NonNullable< + ResultOf['orderById'] +>; + +export type CartLineItem = NonNullable< + NonNullable['orderById']>['lineItems'] +>[number]; + +export type CartOrderTotals = NonNullable< + NonNullable['orderById']>['totals'] +>; + +export type CartLineItemTotals = NonNullable< + NonNullable< + NonNullable['orderById']>['lineItems'] + >[number]['totals'] +>; + +export type CartDiscount = NonNullable< + NonNullable['orderById']>['discounts'] +>[number]; + +export type CartTax = NonNullable< + NonNullable['orderById']>['taxes'] +>[number]; + +export type CartShippingInfo = NonNullable< + NonNullable['orderById']>['shipping'] +>; + +export type CartNote = NonNullable< + NonNullable['orderById']>['notes'] +>[number]; + +export type CartLineItemDetails = NonNullable< + NonNullable< + NonNullable['orderById']>['lineItems'] + >[number]['details'] +>; From d0b078f55e312d4a8e5ad5aa3ccd858bea666775 Mon Sep 17 00:00:00 2001 From: Phil Bennett Date: Mon, 17 Nov 2025 09:07:37 -0600 Subject: [PATCH 02/13] add channel id to provider --- examples/nextjs/app/providers.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/nextjs/app/providers.tsx b/examples/nextjs/app/providers.tsx index 71f3d85a..d96f8d77 100644 --- a/examples/nextjs/app/providers.tsx +++ b/examples/nextjs/app/providers.tsx @@ -25,6 +25,7 @@ export function Providers({ children }: { children: React.ReactNode }) { apiHost={process.env.NEXT_PUBLIC_GODADDY_API_HOST} storeId={process.env.NEXT_PUBLIC_GODADDY_STORE_ID} clientId={process.env.NEXT_PUBLIC_GODADDY_CLIENT_ID} + channelId={process.env.NEXT_PUBLIC_GODADDY_CHANNEL_ID} Link={Link} appearance={{ variables: { primary: '#ff0000', 'primary-foreground': '#FFFFFF' }, From 7f999a4a9945d913dede8a26e3747a77af2b8831 Mon Sep 17 00:00:00 2001 From: Phil Bennett Date: Tue, 18 Nov 2025 08:23:49 -0600 Subject: [PATCH 03/13] update catalog and order schema and endpoints --- .../src/lib/godaddy/catalog-storefront-env.ts | 18 ----- packages/react/src/lib/godaddy/godaddy.ts | 79 +++++++++++++------ .../src/lib/godaddy/orders-storefront-env.ts | 16 ++-- 3 files changed, 67 insertions(+), 46 deletions(-) diff --git a/packages/react/src/lib/godaddy/catalog-storefront-env.ts b/packages/react/src/lib/godaddy/catalog-storefront-env.ts index dcce0046..4ffd98e5 100644 --- a/packages/react/src/lib/godaddy/catalog-storefront-env.ts +++ b/packages/react/src/lib/godaddy/catalog-storefront-env.ts @@ -1325,15 +1325,6 @@ const introspection = { ], isDeprecated: false, }, - { - name: 'status', - type: { - kind: 'SCALAR', - name: 'String', - }, - args: [], - isDeprecated: false, - }, { name: 'updatedAt', type: { @@ -2528,15 +2519,6 @@ const introspection = { ], isDeprecated: false, }, - { - name: 'status', - type: { - kind: 'SCALAR', - name: 'String', - }, - args: [], - isDeprecated: false, - }, { name: 'updatedAt', type: { diff --git a/packages/react/src/lib/godaddy/godaddy.ts b/packages/react/src/lib/godaddy/godaddy.ts index 8eb40479..5d45662b 100644 --- a/packages/react/src/lib/godaddy/godaddy.ts +++ b/packages/react/src/lib/godaddy/godaddy.ts @@ -63,9 +63,13 @@ import { GetCheckoutSessionQuery, } from './checkout-queries.ts'; -function getHostByEnvironment(apiHost?: string, service = 'checkout'): string { +function getHostByEnvironment(apiHost?: string): string { // Use provided apiHost, otherwise default to production - return `https://${service}.commerce.${apiHost || 'api.godaddy.com'}`; + return `https://checkout.commerce.${apiHost || 'api.godaddy.com'}`; +} + +function getApiHostByEnvironment(apiHost?: string, endpoint?: string): string { + return `https://${apiHost || 'api.godaddy.com'}${endpoint ? endpoint : ''}`; } // Type for createCheckoutSession input with kebab-case appearance @@ -1081,10 +1085,13 @@ export function getSkuGroups( clientId: string, apiHost?: string ) { - const GODADDY_HOST = getHostByEnvironment(apiHost, 'catalog'); + const GODADDY_HOST = getApiHostByEnvironment( + apiHost, + `/v2/commerce/stores/${storeId}/catalog-subgraph/storefront` + ); return graphqlRequestWithErrors>( - `${GODADDY_HOST}/storefront`, + GODADDY_HOST, SkuGroupsQuery, input, { @@ -1100,10 +1107,13 @@ export function getSkuGroup( clientId: string, apiHost?: string ) { - const GODADDY_HOST = getHostByEnvironment(apiHost, 'catalog'); + const GODADDY_HOST = getApiHostByEnvironment( + apiHost, + `/v2/commerce/stores/${storeId}/catalog-subgraph/storefront` + ); return graphqlRequestWithErrors>( - `${GODADDY_HOST}/storefront`, + GODADDY_HOST, SkuGroupQuery, input, { @@ -1119,10 +1129,13 @@ export function getSku( clientId: string, apiHost?: string ) { - const GODADDY_HOST = getHostByEnvironment(apiHost, 'catalog'); + const GODADDY_HOST = getApiHostByEnvironment( + apiHost, + `/v2/commerce/stores/${storeId}/catalog-subgraph/storefront` + ); return graphqlRequestWithErrors>( - `${GODADDY_HOST}/storefront`, + GODADDY_HOST, SkuQuery, input, { @@ -1139,10 +1152,13 @@ export function createCartOrder( clientId: string, apiHost?: string ) { - const GODADDY_HOST = getHostByEnvironment(apiHost, 'orders'); + const GODADDY_HOST = getApiHostByEnvironment( + apiHost, + `/v1/commerce/order-storefront-subgraph` + ); return graphqlRequestWithErrors>( - `${GODADDY_HOST}/v1/commerce/order-storefront-subgraph`, + GODADDY_HOST, AddCartOrderMutation, { input }, { @@ -1158,10 +1174,13 @@ export function getCartOrder( clientId: string, apiHost?: string ) { - const GODADDY_HOST = getHostByEnvironment(apiHost, 'orders'); + const GODADDY_HOST = getApiHostByEnvironment( + apiHost, + `/v1/commerce/order-storefront-subgraph` + ); return graphqlRequestWithErrors>( - `${GODADDY_HOST}/v1/commerce/order-storefront-subgraph`, + GODADDY_HOST, GetCartOrderQuery, { id: orderId }, { @@ -1177,10 +1196,13 @@ export function addCartLineItem( clientId: string, apiHost?: string ) { - const GODADDY_HOST = getHostByEnvironment(apiHost, 'orders'); + const GODADDY_HOST = getApiHostByEnvironment( + apiHost, + `/v1/commerce/order-storefront-subgraph` + ); return graphqlRequestWithErrors>( - `${GODADDY_HOST}/v1/commerce/order-storefront-subgraph`, + GODADDY_HOST, AddLineItemBySkuIdMutation, { input }, { @@ -1196,10 +1218,13 @@ export function updateCartOrder( clientId: string, apiHost?: string ) { - const GODADDY_HOST = getHostByEnvironment(apiHost, 'orders'); + const GODADDY_HOST = getApiHostByEnvironment( + apiHost, + `/v1/commerce/order-storefront-subgraph` + ); return graphqlRequestWithErrors>( - `${GODADDY_HOST}/v1/commerce/order-storefront-subgraph`, + GODADDY_HOST, UpdateCartOrderMutation, { input }, { @@ -1215,10 +1240,13 @@ export function deleteCartLineItem( clientId: string, apiHost?: string ) { - const GODADDY_HOST = getHostByEnvironment(apiHost, 'orders'); + const GODADDY_HOST = getApiHostByEnvironment( + apiHost, + `/v1/commerce/order-storefront-subgraph` + ); return graphqlRequestWithErrors>( - `${GODADDY_HOST}/v1/commerce/order-storefront-subgraph`, + GODADDY_HOST, DeleteLineItemByIdMutation, input, { @@ -1234,10 +1262,12 @@ export function applyCartDiscountCodes( clientId: string, apiHost?: string ) { - const GODADDY_HOST = getHostByEnvironment(apiHost, 'orders'); - + const GODADDY_HOST = getApiHostByEnvironment( + apiHost, + `/v1/commerce/order-storefront-subgraph` + ); return graphqlRequestWithErrors>( - `${GODADDY_HOST}/v1/commerce/order-storefront-subgraph`, + GODADDY_HOST, ApplyDiscountCodesMutation, { input }, { @@ -1253,10 +1283,13 @@ export function updateCartLineItem( clientId: string, apiHost?: string ) { - const GODADDY_HOST = getHostByEnvironment(apiHost, 'orders'); + const GODADDY_HOST = getApiHostByEnvironment( + apiHost, + `/v1/commerce/order-storefront-subgraph` + ); return graphqlRequestWithErrors>( - `${GODADDY_HOST}/v1/commerce/order-storefront-subgraph`, + GODADDY_HOST, UpdateLineItemByIdMutation, { input }, { diff --git a/packages/react/src/lib/godaddy/orders-storefront-env.ts b/packages/react/src/lib/godaddy/orders-storefront-env.ts index 3949dcaf..c35d6566 100644 --- a/packages/react/src/lib/godaddy/orders-storefront-env.ts +++ b/packages/react/src/lib/godaddy/orders-storefront-env.ts @@ -64,11 +64,8 @@ const introspection = { { name: 'totals', type: { - kind: 'NON_NULL', - ofType: { - kind: 'INPUT_OBJECT', - name: 'OrderTotalsInput', - }, + kind: 'INPUT_OBJECT', + name: 'OrderTotalsInput', }, }, { @@ -1326,6 +1323,15 @@ const introspection = { args: [], isDeprecated: false, }, + { + name: 'unitAmount', + type: { + kind: 'OBJECT', + name: 'Money', + }, + args: [], + isDeprecated: false, + }, { name: 'updatedAt', type: { From be405c9a66cf650ee8ab6015e19a0b885741e8d5 Mon Sep 17 00:00:00 2001 From: Phil Bennett Date: Tue, 18 Nov 2025 08:24:58 -0600 Subject: [PATCH 04/13] fix first sku lookup --- packages/react/src/components/storefront/product-card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/components/storefront/product-card.tsx b/packages/react/src/components/storefront/product-card.tsx index c3891cd4..3dbf877c 100644 --- a/packages/react/src/components/storefront/product-card.tsx +++ b/packages/react/src/components/storefront/product-card.tsx @@ -38,7 +38,7 @@ export function ProductCard({ // Get first SKU for products without options const firstSku = product?.skus?.edges?.[0]?.node; - const skuId = firstSku?.code || product?.id || ''; + const skuId = firstSku?.id || product?.id || ''; // Use shared add to cart hook const { addToCart, isLoading: isAddingToCart } = useAddToCart({ From 450b672ddf1a1f9432f938024683515a0d1f96dd Mon Sep 17 00:00:00 2001 From: Phil Bennett Date: Tue, 18 Nov 2025 09:01:33 -0600 Subject: [PATCH 05/13] add storefront translations --- packages/localizations/src/deDe.ts | 31 ++++++++++++ packages/localizations/src/enIe.ts | 31 ++++++++++++ packages/localizations/src/enUs.ts | 31 ++++++++++++ packages/localizations/src/esAr.ts | 31 ++++++++++++ packages/localizations/src/esCl.ts | 31 ++++++++++++ packages/localizations/src/esCo.ts | 31 ++++++++++++ packages/localizations/src/esEs.ts | 31 ++++++++++++ packages/localizations/src/esMx.ts | 31 ++++++++++++ packages/localizations/src/esPe.ts | 31 ++++++++++++ packages/localizations/src/esUs.ts | 31 ++++++++++++ packages/localizations/src/frCa.ts | 31 ++++++++++++ packages/localizations/src/frFr.ts | 31 ++++++++++++ packages/localizations/src/idId.ts | 31 ++++++++++++ packages/localizations/src/itIt.ts | 31 ++++++++++++ packages/localizations/src/ptBr.ts | 31 ++++++++++++ packages/localizations/src/qaPs.ts | 31 ++++++++++++ packages/localizations/src/trTr.ts | 31 ++++++++++++ packages/localizations/src/viVn.ts | 31 ++++++++++++ packages/localizations/src/zhCn.ts | 29 +++++++++++ packages/localizations/src/zhSg.ts | 29 +++++++++++ .../react/src/components/storefront/cart.tsx | 14 +++--- .../components/storefront/product-card.tsx | 12 +++-- .../components/storefront/product-details.tsx | 48 +++++++++++-------- .../components/storefront/product-grid.tsx | 7 ++- 24 files changed, 665 insertions(+), 32 deletions(-) diff --git a/packages/localizations/src/deDe.ts b/packages/localizations/src/deDe.ts index 139c2acf..fab8b453 100644 --- a/packages/localizations/src/deDe.ts +++ b/packages/localizations/src/deDe.ts @@ -348,4 +348,35 @@ export const deDe = { DEPENDENCY_ERROR: 'Wir können Ihre Bestellung derzeit nicht bearbeiten. Bitte warten Sie einen Moment und versuchen Sie es erneut', }, + storefront: { + product: 'Produkt', + sale: 'SALE', + noImage: 'Kein Bild', + noImageAvailable: 'Kein Bild verfügbar', + selectOptions: 'Optionen auswählen', + adding: 'Wird hinzugefügt...', + addToCart: 'In den Warenkorb', + shoppingCart: 'Warenkorb', + failedToLoadCart: 'Warenkorb konnte nicht geladen werden:', + retry: 'Wiederholen', + yourCartIsEmpty: 'Ihr Warenkorb ist leer', + addItemsToGetStarted: 'Fügen Sie Artikel hinzu, um zu beginnen', + errorLoadingProducts: 'Fehler beim Laden der Produkte:', + errorLoadingProduct: 'Fehler beim Laden des Produkts:', + productNotFound: 'Produkt nicht gefunden', + loadingVariantDetails: 'Variantendetails werden geladen...', + combinationNotAvailable: + 'Diese Kombination ist nicht verfügbar. Bitte wählen Sie andere Optionen.', + variantsMatch: + 'Varianten entsprechen Ihrer Auswahl. Wählen Sie weitere Attribute aus, um die Auswahl einzugrenzen.', + quantity: 'Menge', + addingToCart: 'Wird zum Warenkorb hinzugefügt...', + outOfStock: 'Nicht vorrätig', + productType: 'Produkttyp:', + productId: 'Produkt-ID:', + selectedSku: 'Ausgewählte SKU:', + stockStatus: 'Lagerstatus:', + lowStock: 'Geringer Lagerbestand', + inStock: 'Auf Lager', + }, }; diff --git a/packages/localizations/src/enIe.ts b/packages/localizations/src/enIe.ts index 04f21481..6be14d2a 100644 --- a/packages/localizations/src/enIe.ts +++ b/packages/localizations/src/enIe.ts @@ -325,4 +325,35 @@ export const enIe = { DEPENDENCY_ERROR: "We're unable to process your order right now. Please wait a moment and try again", }, + storefront: { + product: 'Product', + sale: 'SALE', + noImage: 'No image', + noImageAvailable: 'No image available', + selectOptions: 'Select Options', + adding: 'Adding...', + addToCart: 'Add to Cart', + shoppingCart: 'Shopping Cart', + failedToLoadCart: 'Failed to load cart:', + retry: 'Retry', + yourCartIsEmpty: 'Your cart is empty', + addItemsToGetStarted: 'Add items to get started', + errorLoadingProducts: 'Error loading products:', + errorLoadingProduct: 'Error loading product:', + productNotFound: 'Product not found', + loadingVariantDetails: 'Loading variant details...', + combinationNotAvailable: + 'This combination is not available. Please select different options.', + variantsMatch: + 'variants match your selection. Select more attributes to narrow down.', + quantity: 'Quantity', + addingToCart: 'Adding to Cart...', + outOfStock: 'Out of Stock', + productType: 'Product Type:', + productId: 'Product ID:', + selectedSku: 'Selected SKU:', + stockStatus: 'Stock Status:', + lowStock: 'Low Stock', + inStock: 'In Stock', + }, }; diff --git a/packages/localizations/src/enUs.ts b/packages/localizations/src/enUs.ts index 89d960a6..b2b55e45 100644 --- a/packages/localizations/src/enUs.ts +++ b/packages/localizations/src/enUs.ts @@ -325,4 +325,35 @@ export const enUs = { DEPENDENCY_ERROR: "We're unable to process your order right now. Please wait a moment and try again", }, + storefront: { + product: 'Product', + sale: 'SALE', + noImage: 'No image', + noImageAvailable: 'No image available', + selectOptions: 'Select Options', + adding: 'Adding...', + addToCart: 'Add to Cart', + shoppingCart: 'Shopping Cart', + failedToLoadCart: 'Failed to load cart:', + retry: 'Retry', + yourCartIsEmpty: 'Your cart is empty', + addItemsToGetStarted: 'Add items to get started', + errorLoadingProducts: 'Error loading products:', + errorLoadingProduct: 'Error loading product:', + productNotFound: 'Product not found', + loadingVariantDetails: 'Loading variant details...', + combinationNotAvailable: + 'This combination is not available. Please select different options.', + variantsMatch: + 'variants match your selection. Select more attributes to narrow down.', + quantity: 'Quantity', + addingToCart: 'Adding to Cart...', + outOfStock: 'Out of Stock', + productType: 'Product Type:', + productId: 'Product ID:', + selectedSku: 'Selected SKU:', + stockStatus: 'Stock Status:', + lowStock: 'Low Stock', + inStock: 'In Stock', + }, }; diff --git a/packages/localizations/src/esAr.ts b/packages/localizations/src/esAr.ts index e6f6a692..35ed835c 100644 --- a/packages/localizations/src/esAr.ts +++ b/packages/localizations/src/esAr.ts @@ -331,4 +331,35 @@ export const esAr = { DEPENDENCY_ERROR: 'No podemos procesar su pedido en este momento. Espere un momento e inténtelo de nuevo', }, + storefront: { + product: 'Producto', + sale: 'OFERTA', + noImage: 'Sin imagen', + noImageAvailable: 'Sin imagen disponible', + selectOptions: 'Seleccionar opciones', + adding: 'Agregando...', + addToCart: 'Agregar al carrito', + shoppingCart: 'Carrito de compras', + failedToLoadCart: 'Error al cargar el carrito:', + retry: 'Reintentar', + yourCartIsEmpty: 'Tu carrito está vacío', + addItemsToGetStarted: 'Agregá artículos para comenzar', + errorLoadingProducts: 'Error al cargar productos:', + errorLoadingProduct: 'Error al cargar producto:', + productNotFound: 'Producto no encontrado', + loadingVariantDetails: 'Cargando detalles de la variante...', + combinationNotAvailable: + 'Esta combinación no está disponible. Por favor seleccioná opciones diferentes.', + variantsMatch: + 'variantes coinciden con tu selección. Seleccioná más atributos para reducir las opciones.', + quantity: 'Cantidad', + addingToCart: 'Agregando al carrito...', + outOfStock: 'Agotado', + productType: 'Tipo de producto:', + productId: 'ID del producto:', + selectedSku: 'SKU seleccionado:', + stockStatus: 'Estado del stock:', + lowStock: 'Stock bajo', + inStock: 'En stock', + }, }; diff --git a/packages/localizations/src/esCl.ts b/packages/localizations/src/esCl.ts index ba005740..fc9967e2 100644 --- a/packages/localizations/src/esCl.ts +++ b/packages/localizations/src/esCl.ts @@ -333,4 +333,35 @@ export const esCl = { DEPENDENCY_ERROR: 'No podemos procesar su pedido en este momento. Espere un momento e inténtelo de nuevo', }, + storefront: { + product: 'Producto', + sale: 'OFERTA', + noImage: 'Sin imagen', + noImageAvailable: 'Sin imagen disponible', + selectOptions: 'Seleccionar opciones', + adding: 'Agregando...', + addToCart: 'Agregar al carrito', + shoppingCart: 'Carrito de compras', + failedToLoadCart: 'Error al cargar el carrito:', + retry: 'Reintentar', + yourCartIsEmpty: 'Tu carrito está vacío', + addItemsToGetStarted: 'Agrega artículos para comenzar', + errorLoadingProducts: 'Error al cargar productos:', + errorLoadingProduct: 'Error al cargar producto:', + productNotFound: 'Producto no encontrado', + loadingVariantDetails: 'Cargando detalles de la variante...', + combinationNotAvailable: + 'Esta combinación no está disponible. Por favor selecciona opciones diferentes.', + variantsMatch: + 'variantes coinciden con tu selección. Selecciona más atributos para reducir las opciones.', + quantity: 'Cantidad', + addingToCart: 'Agregando al carrito...', + outOfStock: 'Agotado', + productType: 'Tipo de producto:', + productId: 'ID del producto:', + selectedSku: 'SKU seleccionado:', + stockStatus: 'Estado del stock:', + lowStock: 'Stock bajo', + inStock: 'En stock', + }, }; diff --git a/packages/localizations/src/esCo.ts b/packages/localizations/src/esCo.ts index 0833baae..c11c9421 100644 --- a/packages/localizations/src/esCo.ts +++ b/packages/localizations/src/esCo.ts @@ -331,4 +331,35 @@ export const esCo = { DEPENDENCY_ERROR: 'No podemos procesar su pedido en este momento. Espere un momento e inténtelo de nuevo', }, + storefront: { + product: 'Producto', + sale: 'OFERTA', + noImage: 'Sin imagen', + noImageAvailable: 'Sin imagen disponible', + selectOptions: 'Seleccionar opciones', + adding: 'Agregando...', + addToCart: 'Agregar al carrito', + shoppingCart: 'Carrito de compras', + failedToLoadCart: 'Error al cargar el carrito:', + retry: 'Reintentar', + yourCartIsEmpty: 'Tu carrito está vacío', + addItemsToGetStarted: 'Agrega artículos para comenzar', + errorLoadingProducts: 'Error al cargar productos:', + errorLoadingProduct: 'Error al cargar producto:', + productNotFound: 'Producto no encontrado', + loadingVariantDetails: 'Cargando detalles de la variante...', + combinationNotAvailable: + 'Esta combinación no está disponible. Por favor selecciona opciones diferentes.', + variantsMatch: + 'variantes coinciden con tu selección. Selecciona más atributos para reducir las opciones.', + quantity: 'Cantidad', + addingToCart: 'Agregando al carrito...', + outOfStock: 'Agotado', + productType: 'Tipo de producto:', + productId: 'ID del producto:', + selectedSku: 'SKU seleccionado:', + stockStatus: 'Estado del stock:', + lowStock: 'Stock bajo', + inStock: 'En stock', + }, }; diff --git a/packages/localizations/src/esEs.ts b/packages/localizations/src/esEs.ts index 349a43fe..37739f44 100644 --- a/packages/localizations/src/esEs.ts +++ b/packages/localizations/src/esEs.ts @@ -336,4 +336,35 @@ export const esEs = { DEPENDENCY_ERROR: 'No podemos procesar su pedido en este momento. Espere un momento e inténtelo de nuevo', }, + storefront: { + product: 'Producto', + sale: 'OFERTA', + noImage: 'Sin imagen', + noImageAvailable: 'Sin imagen disponible', + selectOptions: 'Seleccionar opciones', + adding: 'Añadiendo...', + addToCart: 'Añadir al carrito', + shoppingCart: 'Carrito de compras', + failedToLoadCart: 'Error al cargar el carrito:', + retry: 'Reintentar', + yourCartIsEmpty: 'Tu carrito está vacío', + addItemsToGetStarted: 'Añade artículos para comenzar', + errorLoadingProducts: 'Error al cargar productos:', + errorLoadingProduct: 'Error al cargar producto:', + productNotFound: 'Producto no encontrado', + loadingVariantDetails: 'Cargando detalles de la variante...', + combinationNotAvailable: + 'Esta combinación no está disponible. Por favor seleccione opciones diferentes.', + variantsMatch: + 'variantes coinciden con tu selección. Selecciona más atributos para reducir las opciones.', + quantity: 'Cantidad', + addingToCart: 'Añadiendo al carrito...', + outOfStock: 'Agotado', + productType: 'Tipo de producto:', + productId: 'ID del producto:', + selectedSku: 'SKU seleccionado:', + stockStatus: 'Estado del stock:', + lowStock: 'Stock bajo', + inStock: 'En stock', + }, }; diff --git a/packages/localizations/src/esMx.ts b/packages/localizations/src/esMx.ts index e7bd89a1..262cd0c9 100644 --- a/packages/localizations/src/esMx.ts +++ b/packages/localizations/src/esMx.ts @@ -332,4 +332,35 @@ export const esMx = { DEPENDENCY_ERROR: 'No podemos procesar su pedido en este momento. Espere un momento e inténtelo de nuevo', }, + storefront: { + product: 'Producto', + sale: 'OFERTA', + noImage: 'Sin imagen', + noImageAvailable: 'Sin imagen disponible', + selectOptions: 'Seleccionar opciones', + adding: 'Agregando...', + addToCart: 'Agregar al carrito', + shoppingCart: 'Carrito de compras', + failedToLoadCart: 'Error al cargar el carrito:', + retry: 'Reintentar', + yourCartIsEmpty: 'Tu carrito está vacío', + addItemsToGetStarted: 'Agrega artículos para comenzar', + errorLoadingProducts: 'Error al cargar productos:', + errorLoadingProduct: 'Error al cargar producto:', + productNotFound: 'Producto no encontrado', + loadingVariantDetails: 'Cargando detalles de la variante...', + combinationNotAvailable: + 'Esta combinación no está disponible. Por favor selecciona opciones diferentes.', + variantsMatch: + 'variantes coinciden con tu selección. Selecciona más atributos para reducir las opciones.', + quantity: 'Cantidad', + addingToCart: 'Agregando al carrito...', + outOfStock: 'Agotado', + productType: 'Tipo de producto:', + productId: 'ID del producto:', + selectedSku: 'SKU seleccionado:', + stockStatus: 'Estado del stock:', + lowStock: 'Stock bajo', + inStock: 'En stock', + }, }; diff --git a/packages/localizations/src/esPe.ts b/packages/localizations/src/esPe.ts index ef3876e2..37ea59c0 100644 --- a/packages/localizations/src/esPe.ts +++ b/packages/localizations/src/esPe.ts @@ -331,4 +331,35 @@ export const esPe = { DEPENDENCY_ERROR: 'No podemos procesar su pedido en este momento. Espere un momento e inténtelo de nuevo', }, + storefront: { + product: 'Producto', + sale: 'OFERTA', + noImage: 'Sin imagen', + noImageAvailable: 'Sin imagen disponible', + selectOptions: 'Seleccionar opciones', + adding: 'Agregando...', + addToCart: 'Agregar al carrito', + shoppingCart: 'Carrito de compras', + failedToLoadCart: 'Error al cargar el carrito:', + retry: 'Reintentar', + yourCartIsEmpty: 'Tu carrito está vacío', + addItemsToGetStarted: 'Agrega artículos para comenzar', + errorLoadingProducts: 'Error al cargar productos:', + errorLoadingProduct: 'Error al cargar producto:', + productNotFound: 'Producto no encontrado', + loadingVariantDetails: 'Cargando detalles de la variante...', + combinationNotAvailable: + 'Esta combinación no está disponible. Por favor selecciona opciones diferentes.', + variantsMatch: + 'variantes coinciden con tu selección. Selecciona más atributos para reducir las opciones.', + quantity: 'Cantidad', + addingToCart: 'Agregando al carrito...', + outOfStock: 'Agotado', + productType: 'Tipo de producto:', + productId: 'ID del producto:', + selectedSku: 'SKU seleccionado:', + stockStatus: 'Estado del stock:', + lowStock: 'Stock bajo', + inStock: 'En stock', + }, }; diff --git a/packages/localizations/src/esUs.ts b/packages/localizations/src/esUs.ts index 89bbf0fb..b85d4767 100644 --- a/packages/localizations/src/esUs.ts +++ b/packages/localizations/src/esUs.ts @@ -331,4 +331,35 @@ export const esUs = { DEPENDENCY_ERROR: 'No podemos procesar su pedido en este momento. Espere un momento e inténtelo de nuevo', }, + storefront: { + product: 'Producto', + sale: 'OFERTA', + noImage: 'Sin imagen', + noImageAvailable: 'Sin imagen disponible', + selectOptions: 'Seleccionar opciones', + adding: 'Agregando...', + addToCart: 'Agregar al carrito', + shoppingCart: 'Carrito de compras', + failedToLoadCart: 'Error al cargar el carrito:', + retry: 'Reintentar', + yourCartIsEmpty: 'Tu carrito está vacío', + addItemsToGetStarted: 'Agrega artículos para comenzar', + errorLoadingProducts: 'Error al cargar productos:', + errorLoadingProduct: 'Error al cargar producto:', + productNotFound: 'Producto no encontrado', + loadingVariantDetails: 'Cargando detalles de la variante...', + combinationNotAvailable: + 'Esta combinación no está disponible. Por favor selecciona opciones diferentes.', + variantsMatch: + 'variantes coinciden con tu selección. Selecciona más atributos para reducir las opciones.', + quantity: 'Cantidad', + addingToCart: 'Agregando al carrito...', + outOfStock: 'Agotado', + productType: 'Tipo de producto:', + productId: 'ID del producto:', + selectedSku: 'SKU seleccionado:', + stockStatus: 'Estado del stock:', + lowStock: 'Stock bajo', + inStock: 'En stock', + }, }; diff --git a/packages/localizations/src/frCa.ts b/packages/localizations/src/frCa.ts index ae5a2310..800d764f 100644 --- a/packages/localizations/src/frCa.ts +++ b/packages/localizations/src/frCa.ts @@ -348,4 +348,35 @@ export const frCa = { DEPENDENCY_ERROR: 'Nous ne pouvons pas traiter votre commande actuellement. Veuillez patienter un moment et réessayer', }, + storefront: { + product: 'Produit', + sale: 'SOLDE', + noImage: "Pas d'image", + noImageAvailable: 'Aucune image disponible', + selectOptions: 'Sélectionner les options', + adding: 'Ajout en cours...', + addToCart: 'Ajouter au panier', + shoppingCart: 'Panier', + failedToLoadCart: 'Échec du chargement du panier:', + retry: 'Réessayer', + yourCartIsEmpty: 'Votre panier est vide', + addItemsToGetStarted: 'Ajoutez des articles pour commencer', + errorLoadingProducts: 'Erreur lors du chargement des produits:', + errorLoadingProduct: 'Erreur lors du chargement du produit:', + productNotFound: 'Produit non trouvé', + loadingVariantDetails: 'Chargement des détails de la variante...', + combinationNotAvailable: + "Cette combinaison n'est pas disponible. Veuillez sélectionner d'autres options.", + variantsMatch: + "variantes correspondent à votre sélection. Sélectionnez plus d'attributs pour affiner.", + quantity: 'Quantité', + addingToCart: 'Ajout au panier...', + outOfStock: 'Rupture de stock', + productType: 'Type de produit:', + productId: 'ID du produit:', + selectedSku: 'SKU sélectionné:', + stockStatus: 'État du stock:', + lowStock: 'Stock faible', + inStock: 'En stock', + }, }; diff --git a/packages/localizations/src/frFr.ts b/packages/localizations/src/frFr.ts index d4ba9fc9..ca3d5b17 100644 --- a/packages/localizations/src/frFr.ts +++ b/packages/localizations/src/frFr.ts @@ -349,4 +349,35 @@ export const frFr = { DEPENDENCY_ERROR: 'Nous ne pouvons pas traiter votre commande actuellement. Veuillez patienter un moment et réessayer', }, + storefront: { + product: 'Produit', + sale: 'SOLDE', + noImage: "Pas d'image", + noImageAvailable: 'Aucune image disponible', + selectOptions: 'Sélectionner les options', + adding: 'Ajout en cours...', + addToCart: 'Ajouter au panier', + shoppingCart: 'Panier', + failedToLoadCart: 'Échec du chargement du panier:', + retry: 'Réessayer', + yourCartIsEmpty: 'Votre panier est vide', + addItemsToGetStarted: 'Ajoutez des articles pour commencer', + errorLoadingProducts: 'Erreur lors du chargement des produits:', + errorLoadingProduct: 'Erreur lors du chargement du produit:', + productNotFound: 'Produit non trouvé', + loadingVariantDetails: 'Chargement des détails de la variante...', + combinationNotAvailable: + "Cette combinaison n'est pas disponible. Veuillez sélectionner d'autres options.", + variantsMatch: + "variantes correspondent à votre sélection. Sélectionnez plus d'attributs pour affiner.", + quantity: 'Quantité', + addingToCart: 'Ajout au panier...', + outOfStock: 'Rupture de stock', + productType: 'Type de produit:', + productId: 'ID du produit:', + selectedSku: 'SKU sélectionné:', + stockStatus: 'État du stock:', + lowStock: 'Stock faible', + inStock: 'En stock', + }, }; diff --git a/packages/localizations/src/idId.ts b/packages/localizations/src/idId.ts index 29d9aff7..54c6fb86 100644 --- a/packages/localizations/src/idId.ts +++ b/packages/localizations/src/idId.ts @@ -324,4 +324,35 @@ export const idId = { DEPENDENCY_ERROR: 'Kami tidak dapat memproses pesanan Anda saat ini. Silakan tunggu sebentar dan coba lagi', }, + storefront: { + product: 'Produk', + sale: 'OBRAL', + noImage: 'Tidak ada gambar', + noImageAvailable: 'Tidak ada gambar tersedia', + selectOptions: 'Pilih opsi', + adding: 'Menambahkan...', + addToCart: 'Tambahkan ke keranjang', + shoppingCart: 'Keranjang belanja', + failedToLoadCart: 'Gagal memuat keranjang:', + retry: 'Coba lagi', + yourCartIsEmpty: 'Keranjang Anda kosong', + addItemsToGetStarted: 'Tambahkan item untuk memulai', + errorLoadingProducts: 'Error memuat produk:', + errorLoadingProduct: 'Error memuat produk:', + productNotFound: 'Produk tidak ditemukan', + loadingVariantDetails: 'Memuat detail varian...', + combinationNotAvailable: + 'Kombinasi ini tidak tersedia. Silakan pilih opsi yang berbeda.', + variantsMatch: + 'varian cocok dengan pilihan Anda. Pilih lebih banyak atribut untuk mempersempit.', + quantity: 'Jumlah', + addingToCart: 'Menambahkan ke keranjang...', + outOfStock: 'Stok habis', + productType: 'Jenis Produk:', + productId: 'ID Produk:', + selectedSku: 'SKU yang dipilih:', + stockStatus: 'Status Stok:', + lowStock: 'Stok rendah', + inStock: 'Tersedia', + }, }; diff --git a/packages/localizations/src/itIt.ts b/packages/localizations/src/itIt.ts index a26a7298..de0749ee 100644 --- a/packages/localizations/src/itIt.ts +++ b/packages/localizations/src/itIt.ts @@ -348,4 +348,35 @@ export const itIt = { DEPENDENCY_ERROR: 'Non riusciamo a elaborare il tuo ordine in questo momento. Aspetta un momento e riprova', }, + storefront: { + product: 'Prodotto', + sale: 'SALDO', + noImage: 'Nessuna immagine', + noImageAvailable: 'Nessuna immagine disponibile', + selectOptions: 'Seleziona opzioni', + adding: 'Aggiunta in corso...', + addToCart: 'Aggiungi al carrello', + shoppingCart: 'Carrello', + failedToLoadCart: 'Caricamento carrello non riuscito:', + retry: 'Riprova', + yourCartIsEmpty: 'Il tuo carrello è vuoto', + addItemsToGetStarted: 'Aggiungi articoli per iniziare', + errorLoadingProducts: 'Errore nel caricamento dei prodotti:', + errorLoadingProduct: 'Errore nel caricamento del prodotto:', + productNotFound: 'Prodotto non trovato', + loadingVariantDetails: 'Caricamento dei dettagli della variante...', + combinationNotAvailable: + 'Questa combinazione non è disponibile. Si prega di selezionare opzioni diverse.', + variantsMatch: + 'varianti corrispondono alla tua selezione. Seleziona più attributi per restringere.', + quantity: 'Quantità', + addingToCart: 'Aggiunta al carrello...', + outOfStock: 'Esaurito', + productType: 'Tipo di prodotto:', + productId: 'ID prodotto:', + selectedSku: 'SKU selezionato:', + stockStatus: 'Stato delle scorte:', + lowStock: 'Scorte limitate', + inStock: 'Disponibile', + }, }; diff --git a/packages/localizations/src/ptBr.ts b/packages/localizations/src/ptBr.ts index 78fa8072..82df240b 100644 --- a/packages/localizations/src/ptBr.ts +++ b/packages/localizations/src/ptBr.ts @@ -329,4 +329,35 @@ export const ptBr = { DEPENDENCY_ERROR: 'Não conseguimos processar seu pedido no momento. Aguarde um momento e tente novamente', }, + storefront: { + product: 'Produto', + sale: 'PROMOÇÃO', + noImage: 'Sem imagem', + noImageAvailable: 'Nenhuma imagem disponível', + selectOptions: 'Selecionar opções', + adding: 'Adicionando...', + addToCart: 'Adicionar ao carrinho', + shoppingCart: 'Carrinho de compras', + failedToLoadCart: 'Falha ao carregar carrinho:', + retry: 'Tentar novamente', + yourCartIsEmpty: 'Seu carrinho está vazio', + addItemsToGetStarted: 'Adicione itens para começar', + errorLoadingProducts: 'Erro ao carregar produtos:', + errorLoadingProduct: 'Erro ao carregar produto:', + productNotFound: 'Produto não encontrado', + loadingVariantDetails: 'Carregando detalhes da variante...', + combinationNotAvailable: + 'Esta combinação não está disponível. Por favor, selecione opções diferentes.', + variantsMatch: + 'variantes correspondem à sua seleção. Selecione mais atributos para refinar.', + quantity: 'Quantidade', + addingToCart: 'Adicionando ao carrinho...', + outOfStock: 'Fora de estoque', + productType: 'Tipo de produto:', + productId: 'ID do produto:', + selectedSku: 'SKU selecionado:', + stockStatus: 'Status do estoque:', + lowStock: 'Estoque baixo', + inStock: 'Em estoque', + }, }; diff --git a/packages/localizations/src/qaPs.ts b/packages/localizations/src/qaPs.ts index c0e35fa8..1ec49e04 100644 --- a/packages/localizations/src/qaPs.ts +++ b/packages/localizations/src/qaPs.ts @@ -333,4 +333,35 @@ export const qaPs = { DEPENDENCY_ERROR: 'موږ اوس ستاسو امر پروسس نشو کولی. مهرباني وکړئ یو شېبه انتظار وکړئ او بیا هڅه وکړئ', }, + storefront: { + product: '[Product]', + sale: '[SALE]', + noImage: '[No image]', + noImageAvailable: '[No image available]', + selectOptions: '[Select Options]', + adding: '[Adding...]', + addToCart: '[Add to Cart]', + shoppingCart: '[Shopping Cart]', + failedToLoadCart: '[Failed to load cart:]', + retry: '[Retry]', + yourCartIsEmpty: '[Your cart is empty]', + addItemsToGetStarted: '[Add items to get started]', + errorLoadingProducts: '[Error loading products:]', + errorLoadingProduct: '[Error loading product:]', + productNotFound: '[Product not found]', + loadingVariantDetails: '[Loading variant details...]', + combinationNotAvailable: + '[This combination is not available. Please select different options.]', + variantsMatch: + '[variants match your selection. Select more attributes to narrow down.]', + quantity: '[Quantity]', + addingToCart: '[Adding to Cart...]', + outOfStock: '[Out of Stock]', + productType: '[Product Type:]', + productId: '[Product ID:]', + selectedSku: '[Selected SKU:]', + stockStatus: '[Stock Status:]', + lowStock: '[Low Stock]', + inStock: '[In Stock]', + }, }; diff --git a/packages/localizations/src/trTr.ts b/packages/localizations/src/trTr.ts index a2d1a5bd..ed781f54 100644 --- a/packages/localizations/src/trTr.ts +++ b/packages/localizations/src/trTr.ts @@ -324,4 +324,35 @@ export const trTr = { DEPENDENCY_ERROR: 'Şu anda siparişinizi işleme alamıyoruz. Lütfen bir dakika bekleyin ve tekrar deneyin', }, + storefront: { + product: 'Ürün', + sale: 'İNDİRİM', + noImage: 'Resim yok', + noImageAvailable: 'Resim mevcut değil', + selectOptions: 'Seçenekleri seçin', + adding: 'Ekleniyor...', + addToCart: 'Sepete ekle', + shoppingCart: 'Alışveriş sepeti', + failedToLoadCart: 'Sepet yüklenemedi:', + retry: 'Yeniden dene', + yourCartIsEmpty: 'Sepetiniz boş', + addItemsToGetStarted: 'Başlamak için ürün ekleyin', + errorLoadingProducts: 'Ürünler yüklenirken hata:', + errorLoadingProduct: 'Ürün yüklenirken hata:', + productNotFound: 'Ürün bulunamadı', + loadingVariantDetails: 'Varyant detayları yükleniyor...', + combinationNotAvailable: + 'Bu kombinasyon mevcut değil. Lütfen farklı seçenekler seçin.', + variantsMatch: + 'varyant seçiminizle eşleşiyor. Daraltmak için daha fazla özellik seçin.', + quantity: 'Miktar', + addingToCart: 'Sepete ekleniyor...', + outOfStock: 'Stokta yok', + productType: 'Ürün Tipi:', + productId: 'Ürün ID:', + selectedSku: 'Seçili SKU:', + stockStatus: 'Stok Durumu:', + lowStock: 'Düşük stok', + inStock: 'Stokta', + }, }; diff --git a/packages/localizations/src/viVn.ts b/packages/localizations/src/viVn.ts index b5d031f1..2b6b3e82 100644 --- a/packages/localizations/src/viVn.ts +++ b/packages/localizations/src/viVn.ts @@ -325,4 +325,35 @@ export const viVn = { DEPENDENCY_ERROR: 'Chúng tôi không thể xử lý đơn hàng của bạn ngay bây giờ. Vui lòng đợi một chút và thử lại', }, + storefront: { + product: 'Sản phẩm', + sale: 'GIẢM GIÁ', + noImage: 'Không có hình ảnh', + noImageAvailable: 'Không có hình ảnh', + selectOptions: 'Chọn tùy chọn', + adding: 'Đang thêm...', + addToCart: 'Thêm vào giỏ hàng', + shoppingCart: 'Giỏ hàng', + failedToLoadCart: 'Không tải được giỏ hàng:', + retry: 'Thử lại', + yourCartIsEmpty: 'Giỏ hàng của bạn trống', + addItemsToGetStarted: 'Thêm sản phẩm để bắt đầu', + errorLoadingProducts: 'Lỗi tải sản phẩm:', + errorLoadingProduct: 'Lỗi tải sản phẩm:', + productNotFound: 'Không tìm thấy sản phẩm', + loadingVariantDetails: 'Đang tải chi tiết biến thể...', + combinationNotAvailable: + 'Sự kết hợp này không có sẵn. Vui lòng chọn tùy chọn khác.', + variantsMatch: + 'biến thể phù hợp với lựa chọn của bạn. Chọn thêm thuộc tính để thu hẹp.', + quantity: 'Số lượng', + addingToCart: 'Đang thêm vào giỏ hàng...', + outOfStock: 'Hết hàng', + productType: 'Loại sản phẩm:', + productId: 'ID sản phẩm:', + selectedSku: 'SKU đã chọn:', + stockStatus: 'Trạng thái kho:', + lowStock: 'Sắp hết hàng', + inStock: 'Còn hàng', + }, }; diff --git a/packages/localizations/src/zhCn.ts b/packages/localizations/src/zhCn.ts index 7e474e02..c243bc76 100644 --- a/packages/localizations/src/zhCn.ts +++ b/packages/localizations/src/zhCn.ts @@ -314,4 +314,33 @@ export const zhCn = { MISSING_SHIPPING_INFO: '配送地址或方式应用失败', DEPENDENCY_ERROR: '我们目前无法处理您的订单。请稍等片刻再试', }, + storefront: { + product: '产品', + sale: '促销', + noImage: '无图片', + noImageAvailable: '无可用图片', + selectOptions: '选择选项', + adding: '正在添加...', + addToCart: '添加到购物车', + shoppingCart: '购物车', + failedToLoadCart: '加载购物车失败:', + retry: '重试', + yourCartIsEmpty: '您的购物车是空的', + addItemsToGetStarted: '添加商品以开始', + errorLoadingProducts: '加载产品时出错:', + errorLoadingProduct: '加载产品时出错:', + productNotFound: '未找到产品', + loadingVariantDetails: '正在加载变体详情...', + combinationNotAvailable: '此组合不可用。请选择不同的选项。', + variantsMatch: '个变体与您的选择匹配。选择更多属性以缩小范围。', + quantity: '数量', + addingToCart: '正在添加到购物车...', + outOfStock: '缺货', + productType: '产品类型:', + productId: '产品ID:', + selectedSku: '已选SKU:', + stockStatus: '库存状态:', + lowStock: '库存不足', + inStock: '有货', + }, }; diff --git a/packages/localizations/src/zhSg.ts b/packages/localizations/src/zhSg.ts index 6bd0ea7f..b1d14ea1 100644 --- a/packages/localizations/src/zhSg.ts +++ b/packages/localizations/src/zhSg.ts @@ -314,4 +314,33 @@ export const zhSg = { MISSING_SHIPPING_INFO: '配送地址或方式应用失败', DEPENDENCY_ERROR: '我們目前無法處理您的訂單。請稍等片刻再試', }, + storefront: { + product: '产品', + sale: '促销', + noImage: '无图片', + noImageAvailable: '无可用图片', + selectOptions: '选择选项', + adding: '正在添加...', + addToCart: '添加到购物车', + shoppingCart: '购物车', + failedToLoadCart: '加载购物车失败:', + retry: '重试', + yourCartIsEmpty: '您的购物车是空的', + addItemsToGetStarted: '添加商品以开始', + errorLoadingProducts: '加载产品时出错:', + errorLoadingProduct: '加载产品时出错:', + productNotFound: '未找到产品', + loadingVariantDetails: '正在加载变体详情...', + combinationNotAvailable: '此组合不可用。请选择不同的选项。', + variantsMatch: '个变体与您的选择匹配。选择更多属性以缩小范围。', + quantity: '数量', + addingToCart: '正在添加到购物车...', + outOfStock: '缺货', + productType: '产品类型:', + productId: '产品ID:', + selectedSku: '已选SKU:', + stockStatus: '库存状态:', + lowStock: '库存不足', + inStock: '有货', + }, }; diff --git a/packages/react/src/components/storefront/cart.tsx b/packages/react/src/components/storefront/cart.tsx index e125d4ca..7664b2f8 100644 --- a/packages/react/src/components/storefront/cart.tsx +++ b/packages/react/src/components/storefront/cart.tsx @@ -60,11 +60,13 @@ export function Cart({ open, onOpenChange }: CartProps) { const order = cartData?.orderById; + const { t } = useGoDaddyContext(); + // Transform cart line items to Product format for CartLineItems component const items: Product[] = order?.lineItems?.map(item => ({ id: item.id, - name: item.name || 'Product', + name: item.name || t.storefront.product, image: item.details?.productAssetUrl || '', quantity: item.quantity || 0, originalPrice: @@ -100,7 +102,7 @@ export function Cart({ open, onOpenChange }: CartProps) { - Shopping Cart + {t.storefront.shoppingCart}
{isLoading && ( @@ -112,7 +114,7 @@ export function Cart({ open, onOpenChange }: CartProps) { {!isLoading && error && (

- Failed to load cart: {(error as Error).message} + {t.storefront.failedToLoadCart} {(error as Error).message}

)} @@ -131,10 +133,10 @@ export function Cart({ open, onOpenChange }: CartProps) {

- Your cart is empty + {t.storefront.yourCartIsEmpty}

- Add items to get started + {t.storefront.addItemsToGetStarted}

)} diff --git a/packages/react/src/components/storefront/product-card.tsx b/packages/react/src/components/storefront/product-card.tsx index 3dbf877c..f85d526b 100644 --- a/packages/react/src/components/storefront/product-card.tsx +++ b/packages/react/src/components/storefront/product-card.tsx @@ -7,6 +7,7 @@ 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 { useGoDaddyContext } from '@/godaddy-provider'; import { SKUGroup } from '@/types.ts'; interface ProductCardProps { @@ -23,7 +24,8 @@ export function ProductCard({ onAddToCartError, }: ProductCardProps) { const formatCurrency = useFormatCurrency(); - const title = product?.label || product?.name || 'Product'; + const { t } = useGoDaddyContext(); + const title = product?.label || product?.name || t.storefront.product; const description = product?.description || ''; const priceMin = product?.priceRange?.min || 0; const priceMax = product?.priceRange?.max || priceMin; @@ -67,7 +69,7 @@ export function ProductCard({
{isOnSale && ( - SALE + {t.storefront.sale} )} {imageUrl ? ( @@ -78,7 +80,7 @@ export function ProductCard({ /> ) : (
- No image + {t.storefront.noImage}
)}
@@ -101,7 +103,7 @@ export function ProductCard({ {hasOptions ? ( ) : ( @@ -116,7 +118,7 @@ export function ProductCard({ ) : ( )} - {isAddingToCart ? 'Adding...' : 'Add to Cart'} + {isAddingToCart ? t.storefront.adding : t.storefront.addToCart} )}
diff --git a/packages/react/src/components/storefront/product-details.tsx b/packages/react/src/components/storefront/product-details.tsx index e986e7d5..e9352c5a 100644 --- a/packages/react/src/components/storefront/product-details.tsx +++ b/packages/react/src/components/storefront/product-details.tsx @@ -126,6 +126,7 @@ export function ProductDetails({ onAddToCartError, }: ProductDetailsProps) { const context = useGoDaddyContext(); + const { t } = context; const formatCurrency = useFormatCurrency(); // Props take priority over context values @@ -311,7 +312,7 @@ export function ProductDetails({ return (
- Error loading product: {error.message} + {t.storefront.errorLoadingProduct} {error.message}
); @@ -323,13 +324,13 @@ export function ProductDetails({ return (
- Product not found + {t.storefront.productNotFound}
); } - const title = product?.label || product?.name || 'Product'; + const title = product?.label || product?.name || t.storefront.product; const description = product?.description || ''; const htmlDescription = product?.htmlDescription || ''; @@ -413,7 +414,7 @@ export function ProductDetails({
{isOnSale && ( - SALE + {t.storefront.sale} )}
- No image available + {t.storefront.noImageAvailable}
@@ -603,19 +604,17 @@ export function ProductDetails({ {isSkuLoading && (
- Loading variant details... + {t.storefront.loadingVariantDetails}
)} {!isSkuLoading && matchedSkus.length === 0 && (
- This combination is not available. Please select different - options. + {t.storefront.combinationNotAvailable}
)} {!isSkuLoading && matchedSkus.length > 1 && (
- {matchedSkus.length} variants match your selection. Select - more attributes to narrow down. + {matchedSkus.length} {t.storefront.variantsMatch}
)}
@@ -626,7 +625,7 @@ export function ProductDetails({ {/* Quantity Selector */}
@@ -674,7 +673,9 @@ export function ProductDetails({
{product?.type && (
- Product Type: + + {t.storefront.productType} + {product.type} @@ -682,7 +683,9 @@ export function ProductDetails({ )} {product?.id && (
- Product ID: + + {t.storefront.productId} + {product.id} @@ -691,7 +694,9 @@ export function ProductDetails({ {selectedSku && ( <>
- Selected SKU: + + {t.storefront.selectedSku} + {selectedSku.code} @@ -699,17 +704,20 @@ export function ProductDetails({ {selectedSku.inventoryCounts?.edges && selectedSku.inventoryCounts.edges.length > 0 && (
- Stock Status: + + {t.storefront.stockStatus} + {(() => { const availableCount = selectedSku.inventoryCounts.edges.find( edge => edge?.node?.type === 'AVAILABLE' )?.node?.quantity ?? 0; - if (availableCount === 0) return 'Out of Stock'; + if (availableCount === 0) + return t.storefront.outOfStock; if (availableCount < 10) - return `Low Stock (${availableCount})`; - return 'In Stock'; + return `${t.storefront.lowStock} (${availableCount})`; + return t.storefront.inStock; })()}
diff --git a/packages/react/src/components/storefront/product-grid.tsx b/packages/react/src/components/storefront/product-grid.tsx index 8b0c44f5..d73607f0 100644 --- a/packages/react/src/components/storefront/product-grid.tsx +++ b/packages/react/src/components/storefront/product-grid.tsx @@ -40,6 +40,7 @@ export function ProductGrid({ onAddToCartError, }: ProductGridProps) { const context = useGoDaddyContext(); + const { t } = context; const storeId = storeIdProp || context.storeId; const clientId = clientIdProp || context.clientId; @@ -55,7 +56,11 @@ export function ProductGrid({ } if (error) { - return
Error loading products: {error.message}
; + return ( +
+ {t.storefront.errorLoadingProducts} {error.message} +
+ ); } const skuGroups = data?.skuGroups?.edges; From 4f167ceb47707136b106b556f96ebeebc0780938 Mon Sep 17 00:00:00 2001 From: Phil Bennett Date: Tue, 18 Nov 2025 20:33:20 -0600 Subject: [PATCH 06/13] use id instead of sku for addToCart --- .../react/src/components/storefront/cart.tsx | 24 ++++++++++--------- .../components/storefront/product-details.tsx | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/react/src/components/storefront/cart.tsx b/packages/react/src/components/storefront/cart.tsx index 7664b2f8..b0e37a82 100644 --- a/packages/react/src/components/storefront/cart.tsx +++ b/packages/react/src/components/storefront/cart.tsx @@ -69,20 +69,18 @@ export function Cart({ open, onOpenChange }: CartProps) { name: item.name || t.storefront.product, image: item.details?.productAssetUrl || '', quantity: item.quantity || 0, - originalPrice: - (item.totals?.subTotal?.value || 0) / 100 / (item.quantity || 1), - price: (item.totals?.subTotal?.value || 0) / 100 / (item.quantity || 1), - notes: item.notes?.map(note => note.content || '') || [], + originalPrice: (item.totals?.subTotal?.value || 0) / (item.quantity || 1), + price: (item.totals?.subTotal?.value || 0) / (item.quantity || 1), })) || []; // Calculate totals const itemCount = items.reduce((sum, item) => sum + item.quantity, 0); const currencyCode = order?.totals?.total?.currencyCode || 'USD'; - const subtotal = (order?.totals?.subTotal?.value || 0) / 100; - const shipping = (order?.totals?.shippingTotal?.value || 0) / 100; - const taxes = (order?.totals?.taxTotal?.value || 0) / 100; - const discount = (order?.totals?.discountTotal?.value || 0) / 100; - const total = (order?.totals?.total?.value || 0) / 100; + const subtotal = order?.totals?.subTotal?.value || 0; + const shipping = order?.totals?.shippingTotal?.value || 0; + const taxes = order?.totals?.taxTotal?.value || 0; + const discount = order?.totals?.discountTotal?.value || 0; + const total = order?.totals?.total?.value || 0; const totals = { subtotal, @@ -143,8 +141,12 @@ export function Cart({ open, onOpenChange }: CartProps) { {!isLoading && !error && cartOrderId && items.length > 0 && ( <> - - + + )}
diff --git a/packages/react/src/components/storefront/product-details.tsx b/packages/react/src/components/storefront/product-details.tsx index e9352c5a..af25110a 100644 --- a/packages/react/src/components/storefront/product-details.tsx +++ b/packages/react/src/components/storefront/product-details.tsx @@ -399,7 +399,7 @@ export function ProductDetails({ } await addToCart({ - skuId: selectedSku?.code || productId, + skuId: selectedSku?.id || productId, name: title, quantity, productAssetUrl: images[0] || undefined, From 37da108ce88258b740acebbdee08661aa3fb5e2a Mon Sep 17 00:00:00 2001 From: Phil Bennett Date: Wed, 19 Nov 2025 09:14:12 -0600 Subject: [PATCH 07/13] add remove from cart functionality --- packages/localizations/src/deDe.ts | 2 ++ packages/localizations/src/enIe.ts | 2 ++ packages/localizations/src/enUs.ts | 2 ++ packages/localizations/src/esAr.ts | 2 ++ packages/localizations/src/esCl.ts | 2 ++ packages/localizations/src/esCo.ts | 2 ++ packages/localizations/src/esEs.ts | 2 ++ packages/localizations/src/esMx.ts | 2 ++ packages/localizations/src/esPe.ts | 2 ++ packages/localizations/src/esUs.ts | 2 ++ packages/localizations/src/frCa.ts | 2 ++ packages/localizations/src/frFr.ts | 2 ++ packages/localizations/src/idId.ts | 2 ++ packages/localizations/src/itIt.ts | 2 ++ packages/localizations/src/ptBr.ts | 2 ++ packages/localizations/src/qaPs.ts | 2 ++ packages/localizations/src/trTr.ts | 2 ++ packages/localizations/src/viVn.ts | 2 ++ packages/localizations/src/zhCn.ts | 2 ++ packages/localizations/src/zhSg.ts | 2 ++ .../checkout/line-items/line-items.tsx | 30 ++++++++++++++-- .../react/src/components/storefront/cart.tsx | 34 +++++++++++++++++-- .../components/storefront/product-details.tsx | 8 +++-- packages/react/src/components/ui/sheet.tsx | 2 +- .../lib/godaddy/catalog-storefront-queries.ts | 11 +++--- 25 files changed, 113 insertions(+), 12 deletions(-) diff --git a/packages/localizations/src/deDe.ts b/packages/localizations/src/deDe.ts index fab8b453..a64fb355 100644 --- a/packages/localizations/src/deDe.ts +++ b/packages/localizations/src/deDe.ts @@ -378,5 +378,7 @@ export const deDe = { stockStatus: 'Lagerstatus:', lowStock: 'Geringer Lagerbestand', inStock: 'Auf Lager', + remove: 'Entfernen', + removing: 'Wird entfernt...', }, }; diff --git a/packages/localizations/src/enIe.ts b/packages/localizations/src/enIe.ts index 6be14d2a..1c55e8cd 100644 --- a/packages/localizations/src/enIe.ts +++ b/packages/localizations/src/enIe.ts @@ -355,5 +355,7 @@ export const enIe = { stockStatus: 'Stock Status:', lowStock: 'Low Stock', inStock: 'In Stock', + remove: 'Remove', + removing: 'Removing...', }, }; diff --git a/packages/localizations/src/enUs.ts b/packages/localizations/src/enUs.ts index b2b55e45..ec5e6f5f 100644 --- a/packages/localizations/src/enUs.ts +++ b/packages/localizations/src/enUs.ts @@ -355,5 +355,7 @@ export const enUs = { stockStatus: 'Stock Status:', lowStock: 'Low Stock', inStock: 'In Stock', + remove: 'Remove', + removing: 'Removing...', }, }; diff --git a/packages/localizations/src/esAr.ts b/packages/localizations/src/esAr.ts index 35ed835c..4fbf1e68 100644 --- a/packages/localizations/src/esAr.ts +++ b/packages/localizations/src/esAr.ts @@ -361,5 +361,7 @@ export const esAr = { stockStatus: 'Estado del stock:', lowStock: 'Stock bajo', inStock: 'En stock', + remove: 'Eliminar', + removing: 'Eliminando...', }, }; diff --git a/packages/localizations/src/esCl.ts b/packages/localizations/src/esCl.ts index fc9967e2..8f4c86ea 100644 --- a/packages/localizations/src/esCl.ts +++ b/packages/localizations/src/esCl.ts @@ -363,5 +363,7 @@ export const esCl = { stockStatus: 'Estado del stock:', lowStock: 'Stock bajo', inStock: 'En stock', + remove: 'Eliminar', + removing: 'Eliminando...', }, }; diff --git a/packages/localizations/src/esCo.ts b/packages/localizations/src/esCo.ts index c11c9421..e40258f8 100644 --- a/packages/localizations/src/esCo.ts +++ b/packages/localizations/src/esCo.ts @@ -361,5 +361,7 @@ export const esCo = { stockStatus: 'Estado del stock:', lowStock: 'Stock bajo', inStock: 'En stock', + remove: 'Eliminar', + removing: 'Eliminando...', }, }; diff --git a/packages/localizations/src/esEs.ts b/packages/localizations/src/esEs.ts index 37739f44..fa9804ff 100644 --- a/packages/localizations/src/esEs.ts +++ b/packages/localizations/src/esEs.ts @@ -366,5 +366,7 @@ export const esEs = { stockStatus: 'Estado del stock:', lowStock: 'Stock bajo', inStock: 'En stock', + remove: 'Eliminar', + removing: 'Eliminando...', }, }; diff --git a/packages/localizations/src/esMx.ts b/packages/localizations/src/esMx.ts index 262cd0c9..a85c0f0d 100644 --- a/packages/localizations/src/esMx.ts +++ b/packages/localizations/src/esMx.ts @@ -362,5 +362,7 @@ export const esMx = { stockStatus: 'Estado del stock:', lowStock: 'Stock bajo', inStock: 'En stock', + remove: 'Eliminar', + removing: 'Eliminando...', }, }; diff --git a/packages/localizations/src/esPe.ts b/packages/localizations/src/esPe.ts index 37ea59c0..e8bedb0c 100644 --- a/packages/localizations/src/esPe.ts +++ b/packages/localizations/src/esPe.ts @@ -361,5 +361,7 @@ export const esPe = { stockStatus: 'Estado del stock:', lowStock: 'Stock bajo', inStock: 'En stock', + remove: 'Eliminar', + removing: 'Eliminando...', }, }; diff --git a/packages/localizations/src/esUs.ts b/packages/localizations/src/esUs.ts index b85d4767..663bc2cc 100644 --- a/packages/localizations/src/esUs.ts +++ b/packages/localizations/src/esUs.ts @@ -361,5 +361,7 @@ export const esUs = { stockStatus: 'Estado del stock:', lowStock: 'Stock bajo', inStock: 'En stock', + remove: 'Eliminar', + removing: 'Eliminando...', }, }; diff --git a/packages/localizations/src/frCa.ts b/packages/localizations/src/frCa.ts index 800d764f..fa6c31bd 100644 --- a/packages/localizations/src/frCa.ts +++ b/packages/localizations/src/frCa.ts @@ -378,5 +378,7 @@ export const frCa = { stockStatus: 'État du stock:', lowStock: 'Stock faible', inStock: 'En stock', + remove: 'Supprimer', + removing: 'Suppression...', }, }; diff --git a/packages/localizations/src/frFr.ts b/packages/localizations/src/frFr.ts index ca3d5b17..b713d5c7 100644 --- a/packages/localizations/src/frFr.ts +++ b/packages/localizations/src/frFr.ts @@ -379,5 +379,7 @@ export const frFr = { stockStatus: 'État du stock:', lowStock: 'Stock faible', inStock: 'En stock', + remove: 'Supprimer', + removing: 'Suppression...', }, }; diff --git a/packages/localizations/src/idId.ts b/packages/localizations/src/idId.ts index 54c6fb86..4c9b7359 100644 --- a/packages/localizations/src/idId.ts +++ b/packages/localizations/src/idId.ts @@ -354,5 +354,7 @@ export const idId = { stockStatus: 'Status Stok:', lowStock: 'Stok rendah', inStock: 'Tersedia', + remove: 'Hapus', + removing: 'Menghapus...', }, }; diff --git a/packages/localizations/src/itIt.ts b/packages/localizations/src/itIt.ts index de0749ee..9075b37f 100644 --- a/packages/localizations/src/itIt.ts +++ b/packages/localizations/src/itIt.ts @@ -378,5 +378,7 @@ export const itIt = { stockStatus: 'Stato delle scorte:', lowStock: 'Scorte limitate', inStock: 'Disponibile', + remove: 'Rimuovi', + removing: 'Rimozione...', }, }; diff --git a/packages/localizations/src/ptBr.ts b/packages/localizations/src/ptBr.ts index 82df240b..79f8f578 100644 --- a/packages/localizations/src/ptBr.ts +++ b/packages/localizations/src/ptBr.ts @@ -359,5 +359,7 @@ export const ptBr = { stockStatus: 'Status do estoque:', lowStock: 'Estoque baixo', inStock: 'Em estoque', + remove: 'Remover', + removing: 'Removendo...', }, }; diff --git a/packages/localizations/src/qaPs.ts b/packages/localizations/src/qaPs.ts index 1ec49e04..e50955de 100644 --- a/packages/localizations/src/qaPs.ts +++ b/packages/localizations/src/qaPs.ts @@ -363,5 +363,7 @@ export const qaPs = { stockStatus: '[Stock Status:]', lowStock: '[Low Stock]', inStock: '[In Stock]', + remove: '[Remove]', + removing: '[Removing...]', }, }; diff --git a/packages/localizations/src/trTr.ts b/packages/localizations/src/trTr.ts index ed781f54..83b78abc 100644 --- a/packages/localizations/src/trTr.ts +++ b/packages/localizations/src/trTr.ts @@ -354,5 +354,7 @@ export const trTr = { stockStatus: 'Stok Durumu:', lowStock: 'Düşük stok', inStock: 'Stokta', + remove: 'Kaldır', + removing: 'Kaldırılıyor...', }, }; diff --git a/packages/localizations/src/viVn.ts b/packages/localizations/src/viVn.ts index 2b6b3e82..5b5f0569 100644 --- a/packages/localizations/src/viVn.ts +++ b/packages/localizations/src/viVn.ts @@ -355,5 +355,7 @@ export const viVn = { stockStatus: 'Trạng thái kho:', lowStock: 'Sắp hết hàng', inStock: 'Còn hàng', + remove: 'Xóa', + removing: 'Đang xóa...', }, }; diff --git a/packages/localizations/src/zhCn.ts b/packages/localizations/src/zhCn.ts index c243bc76..97813168 100644 --- a/packages/localizations/src/zhCn.ts +++ b/packages/localizations/src/zhCn.ts @@ -342,5 +342,7 @@ export const zhCn = { stockStatus: '库存状态:', lowStock: '库存不足', inStock: '有货', + remove: '删除', + removing: '正在删除...', }, }; diff --git a/packages/localizations/src/zhSg.ts b/packages/localizations/src/zhSg.ts index b1d14ea1..bc537009 100644 --- a/packages/localizations/src/zhSg.ts +++ b/packages/localizations/src/zhSg.ts @@ -342,5 +342,7 @@ export const zhSg = { stockStatus: '库存状态:', lowStock: '库存不足', inStock: '有货', + remove: '删除', + removing: '正在删除...', }, }; diff --git a/packages/react/src/components/checkout/line-items/line-items.tsx b/packages/react/src/components/checkout/line-items/line-items.tsx index 45a92786..196e9315 100644 --- a/packages/react/src/components/checkout/line-items/line-items.tsx +++ b/packages/react/src/components/checkout/line-items/line-items.tsx @@ -2,6 +2,7 @@ import { Image } from 'lucide-react'; import { useFormatCurrency } from '@/components/checkout/utils/format-currency'; +import { Button } from '@/components/ui/button.tsx'; import { useGoDaddyContext } from '@/godaddy-provider'; import type { SKUProduct } from '@/types'; @@ -57,12 +58,18 @@ export interface DraftOrderLineItemsProps { items: Product[]; currencyCode?: string; inputInMinorUnits?: boolean; + onRemoveFromCart?: (itemId: string) => void; + isRemovingFromCart?: boolean; + removingItemId?: string; } export function DraftOrderLineItems({ items, currencyCode = 'USD', inputInMinorUnits = false, + onRemoveFromCart, + isRemovingFromCart = false, + removingItemId, }: DraftOrderLineItemsProps) { const { t } = useGoDaddyContext(); const formatCurrency = useFormatCurrency(); @@ -126,12 +133,29 @@ export function DraftOrderLineItems({ ) : null} - {t.general.quantity}: {item.quantity} + {t.general.quantity}: {item.quantity} ·{' '} + {onRemoveFromCart ? ( + + ) : null}
{item.originalPrice && item.quantity ? ( -
-
+
+
{formatCurrency({ amount: item.originalPrice * item.quantity, diff --git a/packages/react/src/components/storefront/cart.tsx b/packages/react/src/components/storefront/cart.tsx index b0e37a82..5b2e6169 100644 --- a/packages/react/src/components/storefront/cart.tsx +++ b/packages/react/src/components/storefront/cart.tsx @@ -44,7 +44,7 @@ export function Cart({ open, onOpenChange }: CartProps) { }); // Delete line item mutation - const _deleteMutation = useMutation({ + const deleteMutation = useMutation({ mutationFn: (lineItemId: string) => deleteCartLineItem( { id: lineItemId, orderId: cartOrderId! }, @@ -58,6 +58,10 @@ export function Cart({ open, onOpenChange }: CartProps) { }, }); + const handleRemoveFromCart = (itemId: string) => { + deleteMutation.mutate(itemId); + }; + const order = cartData?.orderById; const { t } = useGoDaddyContext(); @@ -71,6 +75,24 @@ export function Cart({ open, onOpenChange }: CartProps) { quantity: item.quantity || 0, originalPrice: (item.totals?.subTotal?.value || 0) / (item.quantity || 1), price: (item.totals?.subTotal?.value || 0) / (item.quantity || 1), + selectedOptions: + item?.details?.selectedOptions?.map(option => ({ + attribute: option.attribute || '', + values: option.values || [], + })) || [], + addons: item.details?.selectedAddons?.map(addon => ({ + attribute: addon.attribute || '', + sku: addon.sku || '', + values: addon.values?.map(value => ({ + costAdjustment: value.costAdjustment + ? { + currencyCode: value.costAdjustment.currencyCode ?? undefined, + value: value.costAdjustment.value ?? undefined, + } + : undefined, + name: value.name ?? undefined, + })), + })), })) || []; // Calculate totals @@ -145,8 +167,16 @@ export function Cart({ open, onOpenChange }: CartProps) { items={items} currencyCode={currencyCode} inputInMinorUnits + onRemoveFromCart={handleRemoveFromCart} + isRemovingFromCart={deleteMutation.isPending} + removingItemId={deleteMutation.variables} + /> + - )}
diff --git a/packages/react/src/components/storefront/product-details.tsx b/packages/react/src/components/storefront/product-details.tsx index af25110a..7362ef5c 100644 --- a/packages/react/src/components/storefront/product-details.tsx +++ b/packages/react/src/components/storefront/product-details.tsx @@ -230,7 +230,11 @@ export function ProductDetails({ ], queryFn: () => getSkuGroup( - { id: productId!, attributeValues: selectedAttributeValues }, + { + id: productId!, + attributeValues: selectedAttributeValues, + ...(!selectedAttributeValues.length ? { first: 1 } : {}), + }, storeId!, clientId!, context?.apiHost @@ -399,7 +403,7 @@ export function ProductDetails({ } await addToCart({ - skuId: selectedSku?.id || productId, + skuId: selectedSku?.id || product?.skus?.edges?.[0]?.node?.id || '', name: title, quantity, productAssetUrl: images[0] || undefined, diff --git a/packages/react/src/components/ui/sheet.tsx b/packages/react/src/components/ui/sheet.tsx index 408e6676..f867253c 100644 --- a/packages/react/src/components/ui/sheet.tsx +++ b/packages/react/src/components/ui/sheet.tsx @@ -68,7 +68,7 @@ const SheetContent = React.forwardRef< className={cn(sheetVariants({ side }), className)} {...props} > - + {t.ui.accessibility.close} diff --git a/packages/react/src/lib/godaddy/catalog-storefront-queries.ts b/packages/react/src/lib/godaddy/catalog-storefront-queries.ts index fc1f4b02..9b608366 100644 --- a/packages/react/src/lib/godaddy/catalog-storefront-queries.ts +++ b/packages/react/src/lib/godaddy/catalog-storefront-queries.ts @@ -1,7 +1,7 @@ import { graphql } from '@/lib/gql/gql-catalog-storefront.tada.ts'; export const SkuGroupsQuery = graphql(` - query SkuGroups($first: Int, $after: String, $id: SKUGroupIdsFilter, $attributeValues: [String!] = []) { + query SkuGroups($first: Int, $after: String, $id: SKUGroupIdsFilter) { skuGroups(first: $first, after: $after, id: $id) { edges { cursor @@ -48,7 +48,7 @@ export const SkuGroupsQuery = graphql(` } } } - skus(attributeValues: { has: $attributeValues }) { + skus(first: 1) { edges { node { id @@ -70,7 +70,7 @@ export const SkuGroupsQuery = graphql(` `); export const SkuGroupQuery = graphql(` - query SkuGroup($id: String!, $attributeValues: [String!] = []) { + query SkuGroup($id: String!, $first: Int, $attributeValues: [String!] = []) { skuGroup(id: $id) { id name @@ -114,7 +114,10 @@ export const SkuGroupQuery = graphql(` } } } - skus(attributeValues: { has: $attributeValues }) { + skus( + attributeValues: { has: $attributeValues } + first: $first + ) { edges { node { id From dc4f398f5ae959548345de2c2302c7213eb9d781 Mon Sep 17 00:00:00 2001 From: Phil Bennett Date: Wed, 19 Nov 2025 11:04:02 -0600 Subject: [PATCH 08/13] add redirect to hosted checkout --- examples/nextjs/app/store/actions.ts | 87 +++++++++++++++++++ examples/nextjs/app/store/layout.tsx | 18 +++- packages/localizations/src/deDe.ts | 1 + packages/localizations/src/enIe.ts | 1 + packages/localizations/src/enUs.ts | 1 + packages/localizations/src/esAr.ts | 1 + packages/localizations/src/esCl.ts | 1 + packages/localizations/src/esCo.ts | 1 + packages/localizations/src/esEs.ts | 1 + packages/localizations/src/esMx.ts | 1 + packages/localizations/src/esPe.ts | 1 + packages/localizations/src/esUs.ts | 1 + packages/localizations/src/frCa.ts | 1 + packages/localizations/src/frFr.ts | 1 + packages/localizations/src/idId.ts | 1 + packages/localizations/src/itIt.ts | 1 + packages/localizations/src/ptBr.ts | 1 + packages/localizations/src/qaPs.ts | 1 + packages/localizations/src/trTr.ts | 1 + packages/localizations/src/viVn.ts | 1 + packages/localizations/src/zhCn.ts | 1 + packages/localizations/src/zhSg.ts | 1 + .../checkout/utils/checkout-transformers.ts | 2 +- .../react/src/components/storefront/cart.tsx | 26 +++++- .../src/lib/godaddy/checkout-mutations.ts | 1 + 25 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 examples/nextjs/app/store/actions.ts diff --git a/examples/nextjs/app/store/actions.ts b/examples/nextjs/app/store/actions.ts new file mode 100644 index 00000000..1cc06f39 --- /dev/null +++ b/examples/nextjs/app/store/actions.ts @@ -0,0 +1,87 @@ +'use server'; + +import { createCheckoutSession } from '@godaddy/react/server'; +import { redirect } from 'next/navigation'; + +export async function checkoutWithOrder(orderId: string) { + const session = await createCheckoutSession( + { + returnUrl: `https://godaddy.com`, + successUrl: `https://godaddy.com/success`, + draftOrderId: orderId, + storeId: process.env.NEXT_PUBLIC_GODADDY_STORE_ID || '', + channelId: process.env.NEXT_PUBLIC_GODADDY_CHANNEL_ID || '', + enableShippingAddressCollection: true, + enableBillingAddressCollection: true, + enableTaxCollection: true, + shipping: { + fulfillmentLocationId: 'default-location', + originAddress: { + addressLine1: '1600 Pennsylvania Ave NW', + adminArea1: 'DC', + adminArea3: 'Washington', + countryCode: 'US', + postalCode: '20500', + }, + }, + taxes: { + originAddress: { + addressLine1: '1600 Pennsylvania Ave NW', + adminArea1: 'DC', + adminArea3: 'Washington', + countryCode: 'US', + postalCode: '20500', + }, + }, + locations: [ + { + id: 'default-location', + isDefault: true, + address: { + addressLine1: '1600 Pennsylvania Ave NW', + adminArea1: 'DC', + adminArea3: 'Washington', + countryCode: 'US', + postalCode: '20500', + }, + }, + ], + paymentMethods: { + card: { + processor: 'godaddy', + checkoutTypes: ['standard'], + }, + express: { + processor: 'godaddy', + checkoutTypes: ['express'], + }, + paypal: { + processor: 'paypal', + checkoutTypes: ['standard'], + }, + offline: { + processor: 'offline', + checkoutTypes: ['standard'], + }, + }, + }, + { + auth: { + clientId: process.env.NEXT_PUBLIC_GODADDY_CLIENT_ID || '', + clientSecret: process.env.GODADDY_CLIENT_SECRET || '', + }, + } + ); + + if (!session) { + throw new Error('Failed to create checkout session'); + } + + console.log({ session }); + + if (!session.url) { + throw new Error('No checkout URL returned'); + } + + redirect(session.url); +} diff --git a/examples/nextjs/app/store/layout.tsx b/examples/nextjs/app/store/layout.tsx index 9b003c0e..a638c781 100644 --- a/examples/nextjs/app/store/layout.tsx +++ b/examples/nextjs/app/store/layout.tsx @@ -3,6 +3,7 @@ import { Cart } from '@godaddy/react'; import { ShoppingCart } from 'lucide-react'; import { createContext, useContext, useState } from 'react'; +import { checkoutWithOrder } from './actions'; interface CartContextType { openCart: () => void; @@ -25,10 +26,20 @@ export default function StoreLayout({ children: React.ReactNode; }) { const [isCartOpen, setIsCartOpen] = useState(false); + const [isCheckingOut, setIsCheckingOut] = useState(false); const openCart = () => setIsCartOpen(true); const closeCart = () => setIsCartOpen(false); + const handleCheckout = async (orderId: string) => { + setIsCheckingOut(true); + try { + await checkoutWithOrder(orderId); + } catch (_error) { + setIsCheckingOut(false); + } + }; + return (
@@ -43,7 +54,12 @@ export default function StoreLayout({ {children} - +
); diff --git a/packages/localizations/src/deDe.ts b/packages/localizations/src/deDe.ts index a64fb355..7ff19ff9 100644 --- a/packages/localizations/src/deDe.ts +++ b/packages/localizations/src/deDe.ts @@ -380,5 +380,6 @@ export const deDe = { inStock: 'Auf Lager', remove: 'Entfernen', removing: 'Wird entfernt...', + checkout: 'Zur Kasse', }, }; diff --git a/packages/localizations/src/enIe.ts b/packages/localizations/src/enIe.ts index 1c55e8cd..aa2fe133 100644 --- a/packages/localizations/src/enIe.ts +++ b/packages/localizations/src/enIe.ts @@ -357,5 +357,6 @@ export const enIe = { inStock: 'In Stock', remove: 'Remove', removing: 'Removing...', + checkout: 'Checkout', }, }; diff --git a/packages/localizations/src/enUs.ts b/packages/localizations/src/enUs.ts index ec5e6f5f..26f1b14b 100644 --- a/packages/localizations/src/enUs.ts +++ b/packages/localizations/src/enUs.ts @@ -357,5 +357,6 @@ export const enUs = { inStock: 'In Stock', remove: 'Remove', removing: 'Removing...', + checkout: 'Checkout', }, }; diff --git a/packages/localizations/src/esAr.ts b/packages/localizations/src/esAr.ts index 4fbf1e68..b0eb370c 100644 --- a/packages/localizations/src/esAr.ts +++ b/packages/localizations/src/esAr.ts @@ -363,5 +363,6 @@ export const esAr = { inStock: 'En stock', remove: 'Eliminar', removing: 'Eliminando...', + checkout: 'Pagar', }, }; diff --git a/packages/localizations/src/esCl.ts b/packages/localizations/src/esCl.ts index 8f4c86ea..8dbbda6b 100644 --- a/packages/localizations/src/esCl.ts +++ b/packages/localizations/src/esCl.ts @@ -365,5 +365,6 @@ export const esCl = { inStock: 'En stock', remove: 'Eliminar', removing: 'Eliminando...', + checkout: 'Pagar', }, }; diff --git a/packages/localizations/src/esCo.ts b/packages/localizations/src/esCo.ts index e40258f8..fd14a3bd 100644 --- a/packages/localizations/src/esCo.ts +++ b/packages/localizations/src/esCo.ts @@ -363,5 +363,6 @@ export const esCo = { inStock: 'En stock', remove: 'Eliminar', removing: 'Eliminando...', + checkout: 'Pagar', }, }; diff --git a/packages/localizations/src/esEs.ts b/packages/localizations/src/esEs.ts index fa9804ff..fab45685 100644 --- a/packages/localizations/src/esEs.ts +++ b/packages/localizations/src/esEs.ts @@ -368,5 +368,6 @@ export const esEs = { inStock: 'En stock', remove: 'Eliminar', removing: 'Eliminando...', + checkout: 'Pagar', }, }; diff --git a/packages/localizations/src/esMx.ts b/packages/localizations/src/esMx.ts index a85c0f0d..54934bf6 100644 --- a/packages/localizations/src/esMx.ts +++ b/packages/localizations/src/esMx.ts @@ -364,5 +364,6 @@ export const esMx = { inStock: 'En stock', remove: 'Eliminar', removing: 'Eliminando...', + checkout: 'Pagar', }, }; diff --git a/packages/localizations/src/esPe.ts b/packages/localizations/src/esPe.ts index e8bedb0c..73eb8e37 100644 --- a/packages/localizations/src/esPe.ts +++ b/packages/localizations/src/esPe.ts @@ -363,5 +363,6 @@ export const esPe = { inStock: 'En stock', remove: 'Eliminar', removing: 'Eliminando...', + checkout: 'Pagar', }, }; diff --git a/packages/localizations/src/esUs.ts b/packages/localizations/src/esUs.ts index 663bc2cc..6739a4fd 100644 --- a/packages/localizations/src/esUs.ts +++ b/packages/localizations/src/esUs.ts @@ -363,5 +363,6 @@ export const esUs = { inStock: 'En stock', remove: 'Eliminar', removing: 'Eliminando...', + checkout: 'Pagar', }, }; diff --git a/packages/localizations/src/frCa.ts b/packages/localizations/src/frCa.ts index fa6c31bd..db7f4eed 100644 --- a/packages/localizations/src/frCa.ts +++ b/packages/localizations/src/frCa.ts @@ -380,5 +380,6 @@ export const frCa = { inStock: 'En stock', remove: 'Supprimer', removing: 'Suppression...', + checkout: 'Commander', }, }; diff --git a/packages/localizations/src/frFr.ts b/packages/localizations/src/frFr.ts index b713d5c7..d0060653 100644 --- a/packages/localizations/src/frFr.ts +++ b/packages/localizations/src/frFr.ts @@ -381,5 +381,6 @@ export const frFr = { inStock: 'En stock', remove: 'Supprimer', removing: 'Suppression...', + checkout: 'Commander', }, }; diff --git a/packages/localizations/src/idId.ts b/packages/localizations/src/idId.ts index 4c9b7359..1f9a6a3b 100644 --- a/packages/localizations/src/idId.ts +++ b/packages/localizations/src/idId.ts @@ -356,5 +356,6 @@ export const idId = { inStock: 'Tersedia', remove: 'Hapus', removing: 'Menghapus...', + checkout: 'Checkout', }, }; diff --git a/packages/localizations/src/itIt.ts b/packages/localizations/src/itIt.ts index 9075b37f..2f2deffc 100644 --- a/packages/localizations/src/itIt.ts +++ b/packages/localizations/src/itIt.ts @@ -380,5 +380,6 @@ export const itIt = { inStock: 'Disponibile', remove: 'Rimuovi', removing: 'Rimozione...', + checkout: 'Acquista', }, }; diff --git a/packages/localizations/src/ptBr.ts b/packages/localizations/src/ptBr.ts index 79f8f578..dbbc6165 100644 --- a/packages/localizations/src/ptBr.ts +++ b/packages/localizations/src/ptBr.ts @@ -361,5 +361,6 @@ export const ptBr = { inStock: 'Em estoque', remove: 'Remover', removing: 'Removendo...', + checkout: 'Finalizar compra', }, }; diff --git a/packages/localizations/src/qaPs.ts b/packages/localizations/src/qaPs.ts index e50955de..c5ce15a3 100644 --- a/packages/localizations/src/qaPs.ts +++ b/packages/localizations/src/qaPs.ts @@ -365,5 +365,6 @@ export const qaPs = { inStock: '[In Stock]', remove: '[Remove]', removing: '[Removing...]', + checkout: '[Checkout]', }, }; diff --git a/packages/localizations/src/trTr.ts b/packages/localizations/src/trTr.ts index 83b78abc..838b47d9 100644 --- a/packages/localizations/src/trTr.ts +++ b/packages/localizations/src/trTr.ts @@ -356,5 +356,6 @@ export const trTr = { inStock: 'Stokta', remove: 'Kaldır', removing: 'Kaldırılıyor...', + checkout: 'Ödeme yap', }, }; diff --git a/packages/localizations/src/viVn.ts b/packages/localizations/src/viVn.ts index 5b5f0569..3c160677 100644 --- a/packages/localizations/src/viVn.ts +++ b/packages/localizations/src/viVn.ts @@ -357,5 +357,6 @@ export const viVn = { inStock: 'Còn hàng', remove: 'Xóa', removing: 'Đang xóa...', + checkout: 'Thanh toán', }, }; diff --git a/packages/localizations/src/zhCn.ts b/packages/localizations/src/zhCn.ts index 97813168..1a1d4a16 100644 --- a/packages/localizations/src/zhCn.ts +++ b/packages/localizations/src/zhCn.ts @@ -344,5 +344,6 @@ export const zhCn = { inStock: '有货', remove: '删除', removing: '正在删除...', + checkout: '结账', }, }; diff --git a/packages/localizations/src/zhSg.ts b/packages/localizations/src/zhSg.ts index bc537009..69fd177f 100644 --- a/packages/localizations/src/zhSg.ts +++ b/packages/localizations/src/zhSg.ts @@ -344,5 +344,6 @@ export const zhSg = { inStock: '有货', remove: '删除', removing: '正在删除...', + checkout: '结账', }, }; diff --git a/packages/react/src/components/checkout/utils/checkout-transformers.ts b/packages/react/src/components/checkout/utils/checkout-transformers.ts index 5382367c..d7d36314 100644 --- a/packages/react/src/components/checkout/utils/checkout-transformers.ts +++ b/packages/react/src/components/checkout/utils/checkout-transformers.ts @@ -157,7 +157,7 @@ export function mapSkusToItemsDisplay( return { id: orderItem.id || '', name: skuDetails?.label || orderItem.name || '', - image: orderItem.details?.productAssetUrl || skuDetails?.mediaUrls?.[0], + image: orderItem.details?.productAssetUrl, quantity: orderItem.quantity || 0, originalPrice: (orderItem.totals?.subTotal?.value ?? 0) / (orderItem.quantity || 0), diff --git a/packages/react/src/components/storefront/cart.tsx b/packages/react/src/components/storefront/cart.tsx index 5b2e6169..99140b58 100644 --- a/packages/react/src/components/storefront/cart.tsx +++ b/packages/react/src/components/storefront/cart.tsx @@ -9,6 +9,7 @@ import { Button } from '@/components/ui/button'; import { Sheet, SheetContent, + SheetFooter, SheetHeader, SheetTitle, } from '@/components/ui/sheet'; @@ -19,9 +20,16 @@ import { deleteCartLineItem, getCartOrder } from '@/lib/godaddy/godaddy'; interface CartProps { open: boolean; onOpenChange: (open: boolean) => void; + onCheckout?: (orderId: string) => void; + isCheckingOut?: boolean; } -export function Cart({ open, onOpenChange }: CartProps) { +export function Cart({ + open, + onOpenChange, + onCheckout, + isCheckingOut = false, +}: CartProps) { const context = useGoDaddyContext(); const queryClient = useQueryClient(); const cartOrderId = getCartOrderId(); @@ -177,6 +185,22 @@ export function Cart({ open, onOpenChange }: CartProps) { enableTaxes={false} enableShipping={false} /> + {onCheckout ? ( + + + + ) : null} )}
diff --git a/packages/react/src/lib/godaddy/checkout-mutations.ts b/packages/react/src/lib/godaddy/checkout-mutations.ts index 23cc9741..d493425c 100644 --- a/packages/react/src/lib/godaddy/checkout-mutations.ts +++ b/packages/react/src/lib/godaddy/checkout-mutations.ts @@ -5,6 +5,7 @@ export const CreateCheckoutSessionMutation = graphql(` createCheckoutSession(input: $input) { id token + url sourceApp returnUrl successUrl From 4f75753acf7e54b0d2633b86743716074cdf09f2 Mon Sep 17 00:00:00 2001 From: Phil Bennett Date: Thu, 20 Nov 2025 13:55:01 -0600 Subject: [PATCH 09/13] add first sku to cart product grid if multi-variant --- packages/localizations/src/deDe.ts | 1 + packages/localizations/src/enIe.ts | 1 + packages/localizations/src/enUs.ts | 1 + packages/localizations/src/esAr.ts | 1 + packages/localizations/src/esCl.ts | 1 + packages/localizations/src/esCo.ts | 1 + packages/localizations/src/esEs.ts | 1 + packages/localizations/src/esMx.ts | 1 + packages/localizations/src/esPe.ts | 1 + packages/localizations/src/esUs.ts | 1 + packages/localizations/src/frCa.ts | 1 + packages/localizations/src/frFr.ts | 1 + packages/localizations/src/idId.ts | 1 + packages/localizations/src/itIt.ts | 1 + packages/localizations/src/ptBr.ts | 1 + packages/localizations/src/qaPs.ts | 1 + packages/localizations/src/trTr.ts | 1 + packages/localizations/src/viVn.ts | 1 + packages/localizations/src/zhCn.ts | 1 + packages/localizations/src/zhSg.ts | 1 + .../components/storefront/product-card.tsx | 86 ++++++++++++++----- .../components/storefront/product-details.tsx | 9 +- packages/react/src/components/ui/badge.tsx | 2 + .../src/lib/godaddy/catalog-storefront-env.ts | 18 ++++ .../lib/godaddy/catalog-storefront-queries.ts | 22 ++++- 25 files changed, 131 insertions(+), 26 deletions(-) diff --git a/packages/localizations/src/deDe.ts b/packages/localizations/src/deDe.ts index 7ff19ff9..8f884e1c 100644 --- a/packages/localizations/src/deDe.ts +++ b/packages/localizations/src/deDe.ts @@ -372,6 +372,7 @@ export const deDe = { quantity: 'Menge', addingToCart: 'Wird zum Warenkorb hinzugefügt...', outOfStock: 'Nicht vorrätig', + viewDetails: 'Details anzeigen', productType: 'Produkttyp:', productId: 'Produkt-ID:', selectedSku: 'Ausgewählte SKU:', diff --git a/packages/localizations/src/enIe.ts b/packages/localizations/src/enIe.ts index aa2fe133..ec6a20ae 100644 --- a/packages/localizations/src/enIe.ts +++ b/packages/localizations/src/enIe.ts @@ -349,6 +349,7 @@ export const enIe = { quantity: 'Quantity', addingToCart: 'Adding to Cart...', outOfStock: 'Out of Stock', + viewDetails: 'View Details', productType: 'Product Type:', productId: 'Product ID:', selectedSku: 'Selected SKU:', diff --git a/packages/localizations/src/enUs.ts b/packages/localizations/src/enUs.ts index 26f1b14b..80fc5793 100644 --- a/packages/localizations/src/enUs.ts +++ b/packages/localizations/src/enUs.ts @@ -349,6 +349,7 @@ export const enUs = { quantity: 'Quantity', addingToCart: 'Adding to Cart...', outOfStock: 'Out of Stock', + viewDetails: 'View Details', productType: 'Product Type:', productId: 'Product ID:', selectedSku: 'Selected SKU:', diff --git a/packages/localizations/src/esAr.ts b/packages/localizations/src/esAr.ts index b0eb370c..eb0df26c 100644 --- a/packages/localizations/src/esAr.ts +++ b/packages/localizations/src/esAr.ts @@ -355,6 +355,7 @@ export const esAr = { quantity: 'Cantidad', addingToCart: 'Agregando al carrito...', outOfStock: 'Agotado', + viewDetails: 'Ver detalles', productType: 'Tipo de producto:', productId: 'ID del producto:', selectedSku: 'SKU seleccionado:', diff --git a/packages/localizations/src/esCl.ts b/packages/localizations/src/esCl.ts index 8dbbda6b..c09cf98f 100644 --- a/packages/localizations/src/esCl.ts +++ b/packages/localizations/src/esCl.ts @@ -357,6 +357,7 @@ export const esCl = { quantity: 'Cantidad', addingToCart: 'Agregando al carrito...', outOfStock: 'Agotado', + viewDetails: 'Ver detalles', productType: 'Tipo de producto:', productId: 'ID del producto:', selectedSku: 'SKU seleccionado:', diff --git a/packages/localizations/src/esCo.ts b/packages/localizations/src/esCo.ts index fd14a3bd..5ee93d15 100644 --- a/packages/localizations/src/esCo.ts +++ b/packages/localizations/src/esCo.ts @@ -355,6 +355,7 @@ export const esCo = { quantity: 'Cantidad', addingToCart: 'Agregando al carrito...', outOfStock: 'Agotado', + viewDetails: 'Ver detalles', productType: 'Tipo de producto:', productId: 'ID del producto:', selectedSku: 'SKU seleccionado:', diff --git a/packages/localizations/src/esEs.ts b/packages/localizations/src/esEs.ts index fab45685..5ce4fd31 100644 --- a/packages/localizations/src/esEs.ts +++ b/packages/localizations/src/esEs.ts @@ -360,6 +360,7 @@ export const esEs = { quantity: 'Cantidad', addingToCart: 'Añadiendo al carrito...', outOfStock: 'Agotado', + viewDetails: 'Ver detalles', productType: 'Tipo de producto:', productId: 'ID del producto:', selectedSku: 'SKU seleccionado:', diff --git a/packages/localizations/src/esMx.ts b/packages/localizations/src/esMx.ts index 54934bf6..cefaa7b1 100644 --- a/packages/localizations/src/esMx.ts +++ b/packages/localizations/src/esMx.ts @@ -356,6 +356,7 @@ export const esMx = { quantity: 'Cantidad', addingToCart: 'Agregando al carrito...', outOfStock: 'Agotado', + viewDetails: 'Ver detalles', productType: 'Tipo de producto:', productId: 'ID del producto:', selectedSku: 'SKU seleccionado:', diff --git a/packages/localizations/src/esPe.ts b/packages/localizations/src/esPe.ts index 73eb8e37..b2c01a8d 100644 --- a/packages/localizations/src/esPe.ts +++ b/packages/localizations/src/esPe.ts @@ -355,6 +355,7 @@ export const esPe = { quantity: 'Cantidad', addingToCart: 'Agregando al carrito...', outOfStock: 'Agotado', + viewDetails: 'Ver detalles', productType: 'Tipo de producto:', productId: 'ID del producto:', selectedSku: 'SKU seleccionado:', diff --git a/packages/localizations/src/esUs.ts b/packages/localizations/src/esUs.ts index 6739a4fd..55fd477a 100644 --- a/packages/localizations/src/esUs.ts +++ b/packages/localizations/src/esUs.ts @@ -355,6 +355,7 @@ export const esUs = { quantity: 'Cantidad', addingToCart: 'Agregando al carrito...', outOfStock: 'Agotado', + viewDetails: 'Ver detalles', productType: 'Tipo de producto:', productId: 'ID del producto:', selectedSku: 'SKU seleccionado:', diff --git a/packages/localizations/src/frCa.ts b/packages/localizations/src/frCa.ts index db7f4eed..e798ec28 100644 --- a/packages/localizations/src/frCa.ts +++ b/packages/localizations/src/frCa.ts @@ -372,6 +372,7 @@ export const frCa = { quantity: 'Quantité', addingToCart: 'Ajout au panier...', outOfStock: 'Rupture de stock', + viewDetails: 'Voir les détails', productType: 'Type de produit:', productId: 'ID du produit:', selectedSku: 'SKU sélectionné:', diff --git a/packages/localizations/src/frFr.ts b/packages/localizations/src/frFr.ts index d0060653..312851b6 100644 --- a/packages/localizations/src/frFr.ts +++ b/packages/localizations/src/frFr.ts @@ -373,6 +373,7 @@ export const frFr = { quantity: 'Quantité', addingToCart: 'Ajout au panier...', outOfStock: 'Rupture de stock', + viewDetails: 'Voir les détails', productType: 'Type de produit:', productId: 'ID du produit:', selectedSku: 'SKU sélectionné:', diff --git a/packages/localizations/src/idId.ts b/packages/localizations/src/idId.ts index 1f9a6a3b..f5206314 100644 --- a/packages/localizations/src/idId.ts +++ b/packages/localizations/src/idId.ts @@ -348,6 +348,7 @@ export const idId = { quantity: 'Jumlah', addingToCart: 'Menambahkan ke keranjang...', outOfStock: 'Stok habis', + viewDetails: 'Lihat Detail', productType: 'Jenis Produk:', productId: 'ID Produk:', selectedSku: 'SKU yang dipilih:', diff --git a/packages/localizations/src/itIt.ts b/packages/localizations/src/itIt.ts index 2f2deffc..20bba839 100644 --- a/packages/localizations/src/itIt.ts +++ b/packages/localizations/src/itIt.ts @@ -372,6 +372,7 @@ export const itIt = { quantity: 'Quantità', addingToCart: 'Aggiunta al carrello...', outOfStock: 'Esaurito', + viewDetails: 'Vedi dettagli', productType: 'Tipo di prodotto:', productId: 'ID prodotto:', selectedSku: 'SKU selezionato:', diff --git a/packages/localizations/src/ptBr.ts b/packages/localizations/src/ptBr.ts index dbbc6165..79cd3b0d 100644 --- a/packages/localizations/src/ptBr.ts +++ b/packages/localizations/src/ptBr.ts @@ -353,6 +353,7 @@ export const ptBr = { quantity: 'Quantidade', addingToCart: 'Adicionando ao carrinho...', outOfStock: 'Fora de estoque', + viewDetails: 'Ver detalhes', productType: 'Tipo de produto:', productId: 'ID do produto:', selectedSku: 'SKU selecionado:', diff --git a/packages/localizations/src/qaPs.ts b/packages/localizations/src/qaPs.ts index c5ce15a3..0edfdcab 100644 --- a/packages/localizations/src/qaPs.ts +++ b/packages/localizations/src/qaPs.ts @@ -357,6 +357,7 @@ export const qaPs = { quantity: '[Quantity]', addingToCart: '[Adding to Cart...]', outOfStock: '[Out of Stock]', + viewDetails: '[View Details]', productType: '[Product Type:]', productId: '[Product ID:]', selectedSku: '[Selected SKU:]', diff --git a/packages/localizations/src/trTr.ts b/packages/localizations/src/trTr.ts index 838b47d9..ae4cb1d1 100644 --- a/packages/localizations/src/trTr.ts +++ b/packages/localizations/src/trTr.ts @@ -348,6 +348,7 @@ export const trTr = { quantity: 'Miktar', addingToCart: 'Sepete ekleniyor...', outOfStock: 'Stokta yok', + viewDetails: 'Detayları Görüntüle', productType: 'Ürün Tipi:', productId: 'Ürün ID:', selectedSku: 'Seçili SKU:', diff --git a/packages/localizations/src/viVn.ts b/packages/localizations/src/viVn.ts index 3c160677..e4660311 100644 --- a/packages/localizations/src/viVn.ts +++ b/packages/localizations/src/viVn.ts @@ -349,6 +349,7 @@ export const viVn = { quantity: 'Số lượng', addingToCart: 'Đang thêm vào giỏ hàng...', outOfStock: 'Hết hàng', + viewDetails: 'Xem chi tiết', productType: 'Loại sản phẩm:', productId: 'ID sản phẩm:', selectedSku: 'SKU đã chọn:', diff --git a/packages/localizations/src/zhCn.ts b/packages/localizations/src/zhCn.ts index 1a1d4a16..595fe1ed 100644 --- a/packages/localizations/src/zhCn.ts +++ b/packages/localizations/src/zhCn.ts @@ -336,6 +336,7 @@ export const zhCn = { quantity: '数量', addingToCart: '正在添加到购物车...', outOfStock: '缺货', + viewDetails: '查看详情', productType: '产品类型:', productId: '产品ID:', selectedSku: '已选SKU:', diff --git a/packages/localizations/src/zhSg.ts b/packages/localizations/src/zhSg.ts index 69fd177f..915dc466 100644 --- a/packages/localizations/src/zhSg.ts +++ b/packages/localizations/src/zhSg.ts @@ -336,6 +336,7 @@ export const zhSg = { quantity: '数量', addingToCart: '正在添加到购物车...', outOfStock: '缺货', + viewDetails: '查看详情', productType: '产品类型:', productId: '产品ID:', selectedSku: '已选SKU:', diff --git a/packages/react/src/components/storefront/product-card.tsx b/packages/react/src/components/storefront/product-card.tsx index f85d526b..107d6442 100644 --- a/packages/react/src/components/storefront/product-card.tsx +++ b/packages/react/src/components/storefront/product-card.tsx @@ -38,10 +38,19 @@ export function ProductCard({ edge => edge?.node?.type === 'IMAGE' )?.node?.url; - // Get first SKU for products without options + // Get first SKU and check inventory const firstSku = product?.skus?.edges?.[0]?.node; const skuId = firstSku?.id || product?.id || ''; + // Check available inventory for first SKU + const inventoryCounts = firstSku?.inventoryCounts?.edges || []; + const availableInventory = + inventoryCounts.find((edge: any) => edge?.node?.type === 'AVAILABLE')?.node + ?.quantity || 0; + + 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, @@ -64,11 +73,63 @@ export function ProductCard({ }); }; + // Determine which button to show based on inventory and variants + const getActionButton = () => { + // If product has options (future use case), show "Select Options" + if (hasOptions) { + return ( + + ); + } + + // If first SKU is out of stock + if (!isFirstSkuInStock) { + // If there are multiple SKUs and an href, suggest viewing details + if (hasMultipleSkus && href) { + return ( + + ); + } + // Otherwise show out of stock button + return ( + + ); + } + + // First SKU is in stock, show "Add to Cart" + return ( + + ); + }; + const cardContent = ( <>
{isOnSale && ( - + {t.storefront.sale} )} @@ -101,26 +162,7 @@ export function ProductCard({ inputInMinorUnits: true, })} - {hasOptions ? ( - - ) : ( - - )} + {getActionButton()}
diff --git a/packages/react/src/components/storefront/product-details.tsx b/packages/react/src/components/storefront/product-details.tsx index 7362ef5c..7e072ca8 100644 --- a/packages/react/src/components/storefront/product-details.tsx +++ b/packages/react/src/components/storefront/product-details.tsx @@ -233,7 +233,7 @@ export function ProductDetails({ { id: productId!, attributeValues: selectedAttributeValues, - ...(!selectedAttributeValues.length ? { first: 1 } : {}), + ...(!selectedAttributeValues.length ? { first: 2 } : {}), }, storeId!, clientId!, @@ -395,7 +395,7 @@ export function ProductDetails({ })() : false; - const canAddToCart = !isOutOfStock && (!attributes.length || selectedSku); + const canAddToCart = !isOutOfStock && Boolean(selectedSku); const handleAddToCart = async () => { if (!canAddToCart) { @@ -417,7 +417,10 @@ export function ProductDetails({ {/* Main Image Carousel */}
{isOnSale && ( - + {t.storefront.sale} )} diff --git a/packages/react/src/components/ui/badge.tsx b/packages/react/src/components/ui/badge.tsx index 19b5addc..47d4ca2f 100644 --- a/packages/react/src/components/ui/badge.tsx +++ b/packages/react/src/components/ui/badge.tsx @@ -13,6 +13,8 @@ const badgeVariants = cva( 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', destructive: 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80', + accent: + 'border-transparent bg-accent text-accent-foreground hover:bg-accent/80', outline: 'text-foreground', }, }, diff --git a/packages/react/src/lib/godaddy/catalog-storefront-env.ts b/packages/react/src/lib/godaddy/catalog-storefront-env.ts index 4ffd98e5..5d61fca3 100644 --- a/packages/react/src/lib/godaddy/catalog-storefront-env.ts +++ b/packages/react/src/lib/godaddy/catalog-storefront-env.ts @@ -8436,6 +8436,24 @@ const introspection = { args: [], isDeprecated: true, }, + { + name: 'skuCount', + type: { + kind: 'SCALAR', + name: 'Int', + }, + args: [ + { + name: 'status', + type: { + kind: 'INPUT_OBJECT', + name: 'SKUStatusFilter', + }, + defaultValue: '{in: ["ACTIVE", "DRAFT"]}', + }, + ], + isDeprecated: false, + }, { name: 'skus', type: { diff --git a/packages/react/src/lib/godaddy/catalog-storefront-queries.ts b/packages/react/src/lib/godaddy/catalog-storefront-queries.ts index 9b608366..0e5a388c 100644 --- a/packages/react/src/lib/godaddy/catalog-storefront-queries.ts +++ b/packages/react/src/lib/godaddy/catalog-storefront-queries.ts @@ -12,6 +12,7 @@ export const SkuGroupsQuery = graphql(` description htmlDescription type +# skuCount(status: { in: ["ACTIVE"] }) priceRange { min max @@ -48,12 +49,21 @@ export const SkuGroupsQuery = graphql(` } } } - skus(first: 1) { + skus(first: 2) { edges { node { id label name + inventoryCounts { + edges { + node { + id + quantity + type + } + } + } } } } @@ -78,6 +88,7 @@ export const SkuGroupQuery = graphql(` description htmlDescription type +# skuCount(status: { in: ["ACTIVE"] }) priceRange { min max @@ -123,6 +134,15 @@ export const SkuGroupQuery = graphql(` id label name + inventoryCounts { + edges { + node { + id + quantity + type + } + } + } } } } From b2495fa6723da35ac82e8006001db95131024d67 Mon Sep 17 00:00:00 2001 From: Phil Bennett Date: Thu, 20 Nov 2025 14:55:41 -0600 Subject: [PATCH 10/13] add tailwind animatino ulitilies --- packages/react/src/globals.css | 74 ++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/packages/react/src/globals.css b/packages/react/src/globals.css index 89152395..ac3016a4 100644 --- a/packages/react/src/globals.css +++ b/packages/react/src/globals.css @@ -1,6 +1,80 @@ @import "tailwindcss" source(none); @source "./components/"; +@keyframes enter { + from { + opacity: 0; + transform: var(--tw-enter-transform); + } +} + +@keyframes exit { + to { + opacity: 0; + transform: var(--tw-exit-transform); + } +} + +@utility animate-in { + animation-name: enter; + animation-duration: 0.15s; + --tw-enter-transform: none; +} + +@utility animate-out { + animation-name: exit; + animation-duration: 0.15s; + --tw-exit-transform: none; +} + +@utility fade-in-0 { + --tw-enter-opacity: 0; +} + +@utility fade-out-0 { + --tw-exit-opacity: 0; +} + +@utility slide-in-from-right { + --tw-enter-transform: translateX(100%); +} + +@utility slide-out-to-right { + --tw-exit-transform: translateX(100%); +} + +@utility slide-in-from-left { + --tw-enter-transform: translateX(-100%); +} + +@utility slide-out-to-left { + --tw-exit-transform: translateX(-100%); +} + +@utility slide-in-from-top { + --tw-enter-transform: translateY(-100%); +} + +@utility slide-out-to-top { + --tw-exit-transform: translateY(-100%); +} + +@utility slide-in-from-bottom { + --tw-enter-transform: translateY(100%); +} + +@utility slide-out-to-bottom { + --tw-exit-transform: translateY(100%); +} + +@utility duration-300 { + animation-duration: 300ms; +} + +@utility duration-500 { + animation-duration: 500ms; +} + @font-face { font-family: "GD Sherpa"; src: url("https://d85ecz8votkqa.cloudfront.net/fonts/gd-sherpa-regular.eot"); /* IE9 Compat Modes */ From be55261d273bb9741d34584fc57ec0dbb34b6226 Mon Sep 17 00:00:00 2001 From: Phil Bennett Date: Thu, 20 Nov 2025 15:53:43 -0600 Subject: [PATCH 11/13] remove unused skuCount --- packages/react/src/lib/godaddy/catalog-storefront-queries.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/react/src/lib/godaddy/catalog-storefront-queries.ts b/packages/react/src/lib/godaddy/catalog-storefront-queries.ts index 0e5a388c..eab1ed43 100644 --- a/packages/react/src/lib/godaddy/catalog-storefront-queries.ts +++ b/packages/react/src/lib/godaddy/catalog-storefront-queries.ts @@ -12,7 +12,6 @@ export const SkuGroupsQuery = graphql(` description htmlDescription type -# skuCount(status: { in: ["ACTIVE"] }) priceRange { min max @@ -88,7 +87,6 @@ export const SkuGroupQuery = graphql(` description htmlDescription type -# skuCount(status: { in: ["ACTIVE"] }) priceRange { min max From c9e0223fce69c37f10c938da22711e9562932e87 Mon Sep 17 00:00:00 2001 From: Wes Cole Date: Thu, 20 Nov 2025 22:24:25 -0500 Subject: [PATCH 12/13] fix: resolve hydration mismatch in Cart and remove internal env references Amp-Thread-ID: https://ampcode.com/threads/T-cdf96c39-faac-4ab4-a89d-1fddcf3b0cb5 Co-authored-by: Amp --- packages/react/src/components/storefront/cart.tsx | 7 ++++++- packages/react/src/godaddy-provider.tsx | 2 -- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/react/src/components/storefront/cart.tsx b/packages/react/src/components/storefront/cart.tsx index 99140b58..9d0472ff 100644 --- a/packages/react/src/components/storefront/cart.tsx +++ b/packages/react/src/components/storefront/cart.tsx @@ -2,6 +2,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Loader2, ShoppingCart } from 'lucide-react'; +import { useEffect, useState } from 'react'; import type { Product } from '@/components/checkout/line-items/line-items'; import { CartLineItems } from '@/components/storefront/cart-line-items'; import { CartTotals } from '@/components/storefront/cart-totals'; @@ -32,7 +33,11 @@ export function Cart({ }: CartProps) { const context = useGoDaddyContext(); const queryClient = useQueryClient(); - const cartOrderId = getCartOrderId(); + const [cartOrderId, setCartOrderId] = useState(null); + + useEffect(() => { + setCartOrderId(getCartOrderId()); + }, []); // Fetch cart order const { diff --git a/packages/react/src/godaddy-provider.tsx b/packages/react/src/godaddy-provider.tsx index 7471a2bf..c13fd245 100644 --- a/packages/react/src/godaddy-provider.tsx +++ b/packages/react/src/godaddy-provider.tsx @@ -102,8 +102,6 @@ export interface GoDaddyProviderProps { * * Internal devs can set to: * - "http://localhost:3000" for local development - * - "https://checkout.commerce.api.dev-godaddy.com" for DEV environment - * - "https://checkout.commerce.api.test-godaddy.com" for TEST environment */ apiHost?: string; clientId?: string; From b097886a9d9c949cc92971d8b12b56b901d03b96 Mon Sep 17 00:00:00 2001 From: Wes Cole Date: Thu, 20 Nov 2025 22:25:27 -0500 Subject: [PATCH 13/13] chore: add changeset for cart functionality Amp-Thread-ID: https://ampcode.com/threads/T-cdf96c39-faac-4ab4-a89d-1fddcf3b0cb5 Co-authored-by: Amp --- .changeset/cart-components-mutations.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/cart-components-mutations.md diff --git a/.changeset/cart-components-mutations.md b/.changeset/cart-components-mutations.md new file mode 100644 index 00000000..74b6a5cf --- /dev/null +++ b/.changeset/cart-components-mutations.md @@ -0,0 +1,7 @@ +--- +"@godaddy/react": patch +--- + +Added cart functionality with `useAddToCart` hook and `Cart` component. +Fixed hydration mismatch in `Cart` component. +Removed internal environment references.