@godaddy/react
Version:
The `createCheckoutSession` function creates a new checkout session with GoDaddy's commerce API.
1,281 lines (1,268 loc) • 63.1 kB
JavaScript
import { D as CheckoutType, E as PaymentMethodRenderer, F as Select, H as Button, I as SelectContent, J as Form, L as SelectItem, M as Skeleton, N as TrackingProvider, O as PaymentMethodType, Q as useIsPaymentDisabled, R as SelectTrigger, T as Target, U as buttonVariants, V as Input, X as FormField, Y as FormControl, Z as FormItem, a as redirectToSuccessUrl, at as useTheme, i as checkoutContext, it as useVariables, j as CheckoutSection, k as PaymentProvider, l as ConditionalExpressProviders, n as LayoutSections, nt as queryClient, o as useCheckoutContext, r as baseCheckoutSchema, rt as useGoDaddyContext, t as Checkout, tt as GoDaddyProvider, v as DraftOrderTotals, w as useFormatCurrency, x as DraftOrderLineItems, z as SelectValue } from "./checkout-B7yB0DfE.js";
import { C as getSku, T as getSkuGroups, a as addCartLineItem, d as createCartOrder, g as getCartOrder, p as deleteCartLineItem, t as cn, w as getSkuGroup } from "./utils-DWBfAHfx.js";
import { ArrowLeft, ArrowRight, ChevronLeftIcon, ChevronRight, ChevronRightIcon, Loader2, Minus, MoreHorizontalIcon, Plus, Search, ShoppingBag, ShoppingCart, X } from "lucide-react";
import * as React$1 from "react";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
import { cva } from "class-variance-authority";
import * as SheetPrimitive from "@radix-ui/react-dialog";
import useEmblaCarousel from "embla-carousel-react";
//#region src/components/checkout/express-checkout/express-checkout.tsx
function DraftOrderExpressCheckoutButtons() {
const { session } = useCheckoutContext();
const availableExpressButtons = React.useMemo(() => {
if (!session?.paymentMethods) return [];
return Object.entries(session.paymentMethods).filter(([, method]) => method && Array.isArray(method.checkoutTypes) && method.checkoutTypes.includes("express")).map(([provider]) => provider);
}, [session?.paymentMethods]).map((provider) => {
const processor = session?.paymentMethods?.[provider]?.processor;
return /* @__PURE__ */ jsx(PaymentMethodRenderer, {
type: "button",
method: provider,
provider: processor
}, `express-${provider}`);
}).filter(Boolean);
if (availableExpressButtons.length === 0) return null;
return /* @__PURE__ */ jsxs(CheckoutSection, {
style: { gridArea: "express-checkout" },
children: [
/* @__PURE__ */ jsx(Target, { id: "checkout.form.express-checkout.before" }),
/* @__PURE__ */ jsx("div", {
className: "flex flex-col gap-3",
children: availableExpressButtons
}),
/* @__PURE__ */ jsx(Target, { id: "checkout.form.express-checkout.after" })
]
});
}
function DraftOrderExpressCheckout(props) {
const { session, enableTracking = false, stripeConfig, godaddyPaymentsConfig, squareConfig, paypalConfig } = props;
useTheme(session?.appearance?.theme);
useVariables(session?.appearance?.variables || props?.appearance?.variables);
const [isConfirmingCheckout, setIsConfirmingCheckout] = React.useState(false);
const [checkoutErrors, setCheckoutErrors] = React.useState(void 0);
const contextValue = {
elements: props?.appearance?.elements,
targets: props?.targets,
session,
stripeConfig,
godaddyPaymentsConfig,
squareConfig,
paypalConfig,
isConfirmingCheckout,
setIsConfirmingCheckout,
checkoutErrors,
setCheckoutErrors
};
if (!React.useMemo(() => {
if (!session?.paymentMethods) return false;
return Object.values(session.paymentMethods).some((method) => method && Array.isArray(method.checkoutTypes) && method.checkoutTypes.includes("express"));
}, [session?.paymentMethods])) return null;
return /* @__PURE__ */ jsx(TrackingProvider, {
session,
trackingEnabled: enableTracking && !!session?.id,
children: /* @__PURE__ */ jsx(checkoutContext.Provider, {
value: contextValue,
children: /* @__PURE__ */ jsx(CheckoutSection, { children: /* @__PURE__ */ jsx(ConditionalExpressProviders, { children: /* @__PURE__ */ jsx(DraftOrderExpressCheckoutButtons, {}) }) })
})
});
}
//#endregion
//#region src/components/storefront/cart-line-items.tsx
function CartLineItems({ ...props }) {
return /* @__PURE__ */ jsx(DraftOrderLineItems, { ...props });
}
//#endregion
//#region src/components/storefront/cart-totals.tsx
function CartTotals({ ...props }) {
return /* @__PURE__ */ jsx(DraftOrderTotals, {
...props,
enableDiscounts: false
});
}
//#endregion
//#region src/components/ui/sheet.tsx
const Sheet = SheetPrimitive.Root;
const SheetTrigger = SheetPrimitive.Trigger;
const SheetClose = SheetPrimitive.Close;
const SheetPortal = SheetPrimitive.Portal;
const SheetOverlay = React$1.forwardRef(({ className,...props }, ref) => /* @__PURE__ */ jsx(SheetPrimitive.Overlay, {
className: cn("fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", className),
...props,
ref
}));
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva("fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out", {
variants: { side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom: "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right: "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm"
} },
defaultVariants: { side: "right" }
});
const SheetContent = React$1.forwardRef(({ side = "right", className, children,...props }, ref) => {
const { t } = useGoDaddyContext();
return /* @__PURE__ */ jsxs(SheetPortal, { children: [/* @__PURE__ */ jsx(SheetOverlay, {}), /* @__PURE__ */ jsxs(SheetPrimitive.Content, {
ref,
className: cn(sheetVariants({ side }), className),
...props,
children: [/* @__PURE__ */ jsxs(SheetPrimitive.Close, {
className: "cursor-pointer absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary",
children: [/* @__PURE__ */ jsx(X, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", {
className: "sr-only",
children: t.ui.accessibility.close
})]
}), children]
})] });
});
SheetContent.displayName = SheetPrimitive.Content.displayName;
const SheetHeader = ({ className,...props }) => /* @__PURE__ */ jsx("div", {
className: cn("flex flex-col space-y-2 text-center sm:text-left", className),
...props
});
SheetHeader.displayName = "SheetHeader";
const SheetFooter = ({ className,...props }) => /* @__PURE__ */ jsx("div", {
className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className),
...props
});
SheetFooter.displayName = "SheetFooter";
const SheetTitle = React$1.forwardRef(({ className,...props }, ref) => /* @__PURE__ */ jsx(SheetPrimitive.Title, {
ref,
className: cn("text-lg font-semibold text-foreground", className),
...props
}));
SheetTitle.displayName = SheetPrimitive.Title.displayName;
const SheetDescription = React$1.forwardRef(({ className,...props }, ref) => /* @__PURE__ */ jsx(SheetPrimitive.Description, {
ref,
className: cn("text-sm text-muted-foreground", className),
...props
}));
SheetDescription.displayName = SheetPrimitive.Description.displayName;
//#endregion
//#region src/lib/cart-storage.ts
const CART_ORDER_ID_KEY = "godaddy_cart_order_id";
const CART_CREATED_AT_KEY = "godaddy_cart_created_at";
const CART_TTL = 720 * 60 * 60 * 1e3;
/**
* Get the cart order ID from localStorage.
* Returns null if the cart doesn't exist or has expired.
*/
function getCartOrderId() {
if (typeof window === "undefined") return null;
const orderId = localStorage.getItem(CART_ORDER_ID_KEY);
const createdAt = localStorage.getItem(CART_CREATED_AT_KEY);
if (!orderId) return null;
if (createdAt) {
if (Date.now() - parseInt(createdAt, 10) > CART_TTL) {
clearCartOrderId();
return null;
}
}
return orderId;
}
/**
* Save the cart order ID to localStorage with a timestamp.
*/
function setCartOrderId(orderId) {
if (typeof window === "undefined") 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.
*/
function clearCartOrderId() {
if (typeof window === "undefined") return;
localStorage.removeItem(CART_ORDER_ID_KEY);
localStorage.removeItem(CART_CREATED_AT_KEY);
}
//#endregion
//#region src/components/storefront/cart.tsx
function Cart({ open, onOpenChange, onCheckout, isCheckingOut = false }) {
const context = useGoDaddyContext();
const queryClient$1 = useQueryClient();
const cartOrderId = getCartOrderId();
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
});
const deleteMutation = useMutation({
mutationFn: (lineItemId) => deleteCartLineItem({
id: lineItemId,
orderId: cartOrderId
}, context.storeId, context.clientId, context?.apiHost),
onSuccess: () => {
queryClient$1.invalidateQueries({ queryKey: ["cart-order", cartOrderId] });
}
});
const handleRemoveFromCart = (itemId) => {
deleteMutation.mutate(itemId);
};
const order = cartData?.orderById;
const { t } = useGoDaddyContext();
const items = order?.lineItems?.map((item) => ({
id: item.id,
name: item.name || t.storefront.product,
image: item.details?.productAssetUrl || "",
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 ?? void 0,
value: value.costAdjustment.value ?? void 0
} : void 0,
name: value.name ?? void 0
}))
}))
})) || [];
const itemCount = items.reduce((sum, item) => sum + item.quantity, 0);
const currencyCode = order?.totals?.total?.currencyCode || "USD";
const subtotal = order?.totals?.subTotal?.value || 0;
const shipping = order?.totals?.shippingTotal?.value || 0;
const taxes = order?.totals?.taxTotal?.value || 0;
const totals = {
subtotal,
discount: order?.totals?.discountTotal?.value || 0,
shipping,
currencyCode,
itemCount,
total: order?.totals?.total?.value || 0,
tip: 0,
taxes,
enableDiscounts: false,
enableTaxes: true,
isTaxLoading: false
};
return /* @__PURE__ */ jsx(Sheet, {
open,
onOpenChange,
children: /* @__PURE__ */ jsxs(SheetContent, {
className: "w-full sm:max-w-lg overflow-y-auto",
children: [/* @__PURE__ */ jsx(SheetHeader, { children: /* @__PURE__ */ jsx(SheetTitle, { children: t.storefront.shoppingCart }) }), /* @__PURE__ */ jsxs("div", {
className: "mt-8 space-y-6",
children: [
isLoading && /* @__PURE__ */ jsx("div", {
className: "flex items-center justify-center py-12",
children: /* @__PURE__ */ jsx(Loader2, { className: "h-8 w-8 animate-spin text-muted-foreground" })
}),
!isLoading && error && /* @__PURE__ */ jsxs("div", {
className: "flex flex-col items-center justify-center py-12 text-center",
children: [/* @__PURE__ */ jsxs("p", {
className: "text-destructive mb-2",
children: [
t.storefront.failedToLoadCart,
" ",
error instanceof Error ? error.message : String(error)
]
}), /* @__PURE__ */ jsx(Button, {
variant: "outline",
onClick: () => queryClient$1.invalidateQueries({ queryKey: ["cart-order", cartOrderId] }),
children: t.storefront.retry
})]
}),
!isLoading && !error && (!cartOrderId || items.length === 0) && /* @__PURE__ */ jsxs("div", {
className: "flex flex-col items-center justify-center py-12 text-center",
children: [
/* @__PURE__ */ jsx(ShoppingCart, { className: "h-16 w-16 text-muted-foreground mb-4" }),
/* @__PURE__ */ jsx("p", {
className: "text-lg font-medium text-foreground mb-1",
children: t.storefront.yourCartIsEmpty
}),
/* @__PURE__ */ jsx("p", {
className: "text-sm text-muted-foreground",
children: t.storefront.addItemsToGetStarted
})
]
}),
!isLoading && !error && cartOrderId && items.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(CartLineItems, {
items,
currencyCode,
inputInMinorUnits: true,
onRemoveFromCart: handleRemoveFromCart,
isRemovingFromCart: deleteMutation.isPending,
removingItemId: deleteMutation.variables
}),
/* @__PURE__ */ jsx(CartTotals, {
...totals,
inputInMinorUnits: true,
enableTaxes: false,
enableShipping: false
}),
onCheckout ? /* @__PURE__ */ jsx(SheetFooter, { children: /* @__PURE__ */ jsx(Button, {
className: "w-full",
size: "lg",
onClick: () => onCheckout(cartOrderId),
disabled: isCheckingOut,
children: isCheckingOut ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : t.storefront.checkout
}) }) : null
] })
]
})]
})
});
}
//#endregion
//#region src/components/storefront/hooks/use-add-to-cart.ts
function useAddToCart(options) {
const context = useGoDaddyContext();
const queryClient$1 = useQueryClient();
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);
}
});
const addLineItemMutation = useMutation({
mutationFn: ({ orderId, input }) => addCartLineItem({
orderId,
skuId: input.skuId,
name: input.name,
quantity: input.quantity,
fulfillmentMode: "NONE",
status: "DRAFT",
details: { productAssetUrl: input.productAssetUrl || void 0 }
}, context.storeId, context.clientId, context?.apiHost),
onSuccess: () => {
queryClient$1.invalidateQueries({ queryKey: ["cart-order"] });
options?.onSuccess?.();
},
onError: (error) => {
options?.onError?.(error);
}
});
const addToCart = async (input) => {
if (!context.storeId || !context.clientId) {
const error = /* @__PURE__ */ new Error("Store ID and Client ID are required");
options?.onError?.(error);
return;
}
let cartOrderId = getCartOrderId();
if (!cartOrderId) {
cartOrderId = (await createCartMutation.mutateAsync()).addDraftOrder?.id || null;
if (!cartOrderId) {
const error = /* @__PURE__ */ new Error("Failed to create cart");
options?.onError?.(error);
return;
}
}
await addLineItemMutation.mutateAsync({
orderId: cartOrderId,
input
});
};
return {
addToCart,
isLoading: createCartMutation.isPending || addLineItemMutation.isPending,
isCreatingCart: createCartMutation.isPending,
isAddingItem: addLineItemMutation.isPending
};
}
//#endregion
//#region src/components/ui/badge.tsx
const badgeVariants = cva("inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", {
variants: { variant: {
default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary: "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"
} },
defaultVariants: { variant: "default" }
});
function Badge({ className, variant,...props }) {
return /* @__PURE__ */ jsx("div", {
className: cn(badgeVariants({ variant }), className),
...props
});
}
//#endregion
//#region src/components/ui/card.tsx
const Card = React$1.forwardRef(({ className,...props }, ref) => /* @__PURE__ */ jsx("div", {
ref,
className: cn("rounded-lg border bg-card text-card-foreground shadow-sm", className),
...props
}));
Card.displayName = "Card";
const CardHeader = React$1.forwardRef(({ className,...props }, ref) => /* @__PURE__ */ jsx("div", {
ref,
className: cn("flex flex-col space-y-1.5 p-6", className),
...props
}));
CardHeader.displayName = "CardHeader";
const CardTitle = React$1.forwardRef(({ className,...props }, ref) => /* @__PURE__ */ jsx("h3", {
ref,
className: cn("text-2xl font-semibold leading-none tracking-tight", className),
...props
}));
CardTitle.displayName = "CardTitle";
const CardDescription = React$1.forwardRef(({ className,...props }, ref) => /* @__PURE__ */ jsx("p", {
ref,
className: cn("text-sm text-muted-foreground", className),
...props
}));
CardDescription.displayName = "CardDescription";
const CardContent = React$1.forwardRef(({ className,...props }, ref) => /* @__PURE__ */ jsx("div", {
ref,
className: cn("p-6 pt-0", className),
...props
}));
CardContent.displayName = "CardContent";
const CardFooter = React$1.forwardRef(({ className,...props }, ref) => /* @__PURE__ */ jsx("div", {
ref,
className: cn("flex items-center p-6 pt-0", className),
...props
}));
CardFooter.displayName = "CardFooter";
//#endregion
//#region src/components/ui/link.tsx
/**
* Default Link implementation that falls back to a regular anchor tag
* if no custom Link component is provided via GoDaddyProvider
*/
const DefaultLink = React.forwardRef(({ href, children,...props }, ref) => {
return /* @__PURE__ */ jsx("a", {
ref,
href,
...props,
children
});
});
DefaultLink.displayName = "DefaultLink";
/**
* RouterLink component that uses the Link component from GoDaddyProvider context
* or falls back to a default anchor implementation
*/
const RouterLink = React.forwardRef((props, ref) => {
const { Link } = useGoDaddyContext();
return /* @__PURE__ */ jsx(Link || DefaultLink, {
ref,
...props
});
});
RouterLink.displayName = "RouterLink";
//#endregion
//#region src/components/storefront/product-card.tsx
function ProductCard({ product: productProp, productId, storeId: storeIdProp, clientId: clientIdProp, href: hrefProp, getProductHref, onAddToCartSuccess, onAddToCartError }) {
const context = useGoDaddyContext();
const { t } = context;
const formatCurrency = useFormatCurrency();
const storeId = storeIdProp || context.storeId;
const clientId = clientIdProp || context.clientId;
const { data: fetchedProductData, isLoading, error } = useQuery({
queryKey: [
"sku-group",
productId,
storeId,
clientId
],
queryFn: () => getSkuGroup({ id: productId }, storeId, clientId, context.apiHost),
enabled: !!productId && !!storeId && !!clientId && !productProp
});
const { addToCart, isLoading: isAddingToCart } = useAddToCart({
onSuccess: onAddToCartSuccess,
onError: onAddToCartError
});
const product = productProp || fetchedProductData?.skuGroup;
const resolvedProductId = product?.id || productId;
const href = hrefProp || (getProductHref && resolvedProductId ? getProductHref(resolvedProductId) : void 0);
if (isLoading && !product) return /* @__PURE__ */ jsxs(Card, {
className: "overflow-hidden border-border flex flex-col h-full",
children: [/* @__PURE__ */ jsx("div", {
className: "aspect-square overflow-hidden bg-muted",
children: /* @__PURE__ */ jsx(Skeleton, { className: "w-full h-full" })
}), /* @__PURE__ */ jsxs("div", {
className: "p-4 space-y-2 flex flex-col flex-1",
children: [
/* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-3/4" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-2/3" }),
/* @__PURE__ */ jsxs("div", {
className: "flex items-center justify-between pt-2 mt-auto",
children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-20" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-24" })]
})
]
})]
});
if (error && !product) {
const message = error instanceof Error ? error.message : String(error);
return /* @__PURE__ */ jsx(Card, {
className: "overflow-hidden border-border flex flex-col h-full",
children: /* @__PURE__ */ jsxs("div", {
className: "p-4 text-sm text-destructive",
children: [
t.storefront.errorLoadingProducts,
" ",
message
]
})
});
}
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;
const compareAtMin = product?.compareAtPriceRange?.min;
const isOnSale = compareAtMin && compareAtMin > priceMin;
const isPriceRange = priceMin !== priceMax;
const imageUrl = product?.mediaObjects?.edges?.find((edge) => edge?.node?.type === "IMAGE")?.node?.url;
const firstSku = product?.skus?.edges?.[0]?.node;
const skuId = firstSku?.id || product?.id || "";
const isFirstSkuInStock = ((firstSku?.inventoryCounts?.edges || []).find((edge) => edge?.node?.type === "AVAILABLE")?.node?.quantity || 0) > 0;
const hasMultipleSkus = (product?.skus?.edges?.length || 0) > 1;
const handleAddToCart = async (e) => {
e.preventDefault();
e.stopPropagation();
if (!skuId) return;
await addToCart({
skuId,
name: title,
quantity: 1,
productAssetUrl: imageUrl || void 0
});
};
const getActionButton = () => {
if (!isFirstSkuInStock) {
if (hasMultipleSkus && href) return /* @__PURE__ */ jsxs(Button, {
size: "sm",
variant: "secondary",
className: "gap-1",
children: [/* @__PURE__ */ jsx("span", { children: t.storefront.viewDetails }), /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })]
});
return /* @__PURE__ */ jsx(Button, {
size: "sm",
variant: "secondary",
disabled: true,
children: t.storefront.outOfStock
});
}
return /* @__PURE__ */ jsxs(Button, {
size: "sm",
onClick: handleAddToCart,
className: "gap-2",
disabled: isAddingToCart,
children: [isAddingToCart ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(ShoppingBag, { className: "h-4 w-4" }), isAddingToCart ? t.storefront.adding : t.storefront.addToCart]
});
};
const cardContent = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
className: "aspect-square overflow-hidden bg-muted relative",
children: [isOnSale && /* @__PURE__ */ jsx(Badge, {
variant: "accent",
className: "absolute top-3 right-3 z-10 font-semibold",
children: t.storefront.sale
}), imageUrl ? /* @__PURE__ */ jsx("img", {
src: imageUrl,
alt: title,
className: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
}) : /* @__PURE__ */ jsx("div", {
className: "w-full h-full flex items-center justify-center text-muted-foreground",
children: t.storefront.noImage
})]
}), /* @__PURE__ */ jsxs("div", {
className: "p-4 space-y-2 flex flex-col flex-1",
children: [
/* @__PURE__ */ jsx("h3", {
className: "font-medium text-foreground group-hover:text-primary transition-colors",
children: title
}),
/* @__PURE__ */ jsx("p", {
className: "text-sm text-muted-foreground line-clamp-2",
children: description
}),
/* @__PURE__ */ jsxs("div", {
className: "flex items-center justify-between pt-2 mt-auto",
children: [/* @__PURE__ */ jsx("span", {
className: "text-md font-semibold text-foreground",
children: isPriceRange ? `${formatCurrency({
amount: priceMin,
currencyCode: "USD",
inputInMinorUnits: true
})} - ${formatCurrency({
amount: priceMax,
currencyCode: "USD",
inputInMinorUnits: true
})}` : formatCurrency({
amount: priceMin,
currencyCode: "USD",
inputInMinorUnits: true
})
}), getActionButton()]
})
]
})] });
if (href) return /* @__PURE__ */ jsx(RouterLink, {
href,
className: "block",
children: /* @__PURE__ */ jsx(Card, {
className: "group overflow-hidden border-border hover:shadow-lg transition-shadow duration-300 flex flex-col h-full",
children: cardContent
})
});
return /* @__PURE__ */ jsx(Card, {
className: "group overflow-hidden border-border hover:shadow-lg transition-shadow duration-300 flex flex-col",
children: cardContent
});
}
//#endregion
//#region src/components/ui/carousel.tsx
const CarouselContext = React$1.createContext(null);
function useCarousel() {
const context = React$1.useContext(CarouselContext);
if (!context) throw new Error("useCarousel must be used within a <Carousel />");
return context;
}
function Carousel({ orientation = "horizontal", opts, setApi, plugins, className, children,...props }) {
const [carouselRef, api] = useEmblaCarousel({
...opts,
axis: orientation === "horizontal" ? "x" : "y"
}, plugins);
const [canScrollPrev, setCanScrollPrev] = React$1.useState(false);
const [canScrollNext, setCanScrollNext] = React$1.useState(false);
const onSelect = React$1.useCallback((carouselApi) => {
if (!carouselApi) return;
setCanScrollPrev(carouselApi.canScrollPrev());
setCanScrollNext(carouselApi.canScrollNext());
}, []);
const scrollPrev = React$1.useCallback(() => {
api?.scrollPrev();
}, [api]);
const scrollNext = React$1.useCallback(() => {
api?.scrollNext();
}, [api]);
const handleKeyDown = React$1.useCallback((event) => {
if (event.key === "ArrowLeft") {
event.preventDefault();
scrollPrev();
} else if (event.key === "ArrowRight") {
event.preventDefault();
scrollNext();
}
}, [scrollPrev, scrollNext]);
React$1.useEffect(() => {
if (!api || !setApi) return;
setApi(api);
}, [api, setApi]);
React$1.useEffect(() => {
if (!api) return;
onSelect(api);
api.on("reInit", onSelect);
api.on("select", onSelect);
return () => {
api?.off("select", onSelect);
};
}, [api, onSelect]);
return /* @__PURE__ */ jsx(CarouselContext.Provider, {
value: {
carouselRef,
api,
opts,
orientation: orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext
},
children: /* @__PURE__ */ jsx("div", {
onKeyDownCapture: handleKeyDown,
className: cn("relative", className),
role: "region",
"aria-roledescription": "carousel",
"data-slot": "carousel",
...props,
children
})
});
}
function CarouselContent({ className,...props }) {
const { carouselRef, orientation } = useCarousel();
return /* @__PURE__ */ jsx("div", {
ref: carouselRef,
className: "overflow-hidden",
"data-slot": "carousel-content",
children: /* @__PURE__ */ jsx("div", {
className: cn("flex", orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col", className),
...props
})
});
}
function CarouselItem({ className,...props }) {
const { orientation } = useCarousel();
return /* @__PURE__ */ jsx("div", {
role: "group",
"aria-roledescription": "slide",
"data-slot": "carousel-item",
className: cn("min-w-0 shrink-0 grow-0 basis-full", orientation === "horizontal" ? "pl-4" : "pt-4", className),
...props
});
}
function CarouselPrevious({ className, variant = "outline", size = "icon",...props }) {
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
return /* @__PURE__ */ jsxs(Button, {
"data-slot": "carousel-previous",
variant,
size,
className: cn("absolute size-8 rounded-full", orientation === "horizontal" ? "top-1/2 -left-12 -translate-y-1/2" : "-top-12 left-1/2 -translate-x-1/2 rotate-90", className),
disabled: !canScrollPrev,
onClick: scrollPrev,
...props,
children: [/* @__PURE__ */ jsx(ArrowLeft, {}), /* @__PURE__ */ jsx("span", {
className: "sr-only",
children: "Previous slide"
})]
});
}
function CarouselNext({ className, variant = "outline", size = "icon",...props }) {
const { orientation, scrollNext, canScrollNext } = useCarousel();
return /* @__PURE__ */ jsxs(Button, {
"data-slot": "carousel-next",
variant,
size,
className: cn("absolute size-8 rounded-full", orientation === "horizontal" ? "top-1/2 -right-12 -translate-y-1/2" : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90", className),
disabled: !canScrollNext,
onClick: scrollNext,
...props,
children: [/* @__PURE__ */ jsx(ArrowRight, {}), /* @__PURE__ */ jsx("span", {
className: "sr-only",
children: "Next slide"
})]
});
}
//#endregion
//#region src/components/storefront/product-details.tsx
/**
* TODO: Product Details Enhancements
*
* 1. Variant SKU Management
* - [ ] Fetch individual variant SKUs based on selected attribute combinations
* - [ ] Query SKU-level data when attributes change (size, color, etc.)
* - [ ] Update product price and images when variant changes
* - [ ] Handle variant-specific media objects
*
* 2. Inventory Management
* - [ ] Check real-time inventory levels for the selected variant
* - [ ] Display available quantity or low stock warnings
* - [ ] Integrate inventory API calls
* - [ ] Cache inventory data with appropriate TTL
*
* 3. Out of Stock UI
* - [ ] Add "Out of Stock" badge when inventory is depleted
* - [ ] Disable "Add to Cart" button for out-of-stock items
* - [ ] Show "Notify When Available" option for out-of-stock products
* - [ ] Display estimated restock date if available
*
* 4. Unavailable Variant UI
* - [ ] Disable attribute options that result in unavailable variants
* - [ ] Show strikethrough or greyed-out style for unavailable options
* - [ ] Display tooltip explaining why option is unavailable
* - [ ] Prevent selection of invalid attribute combinations
* - [ ] Show nearest available alternative when variant is unavailable
*/
function ProductDetailsSkeleton() {
return /* @__PURE__ */ jsxs("div", {
className: "grid md:grid-cols-2 gap-8 p-4",
children: [/* @__PURE__ */ jsxs("div", {
className: "space-y-4",
children: [/* @__PURE__ */ jsx("div", {
className: "relative",
children: /* @__PURE__ */ jsx(Card, {
className: "overflow-hidden aspect-square bg-muted border-border",
children: /* @__PURE__ */ jsx(Skeleton, { className: "w-full h-full" })
})
}), /* @__PURE__ */ jsx("div", {
className: "grid grid-cols-4 gap-2",
children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsx("div", {
className: "aspect-square w-full rounded-md border border-input overflow-hidden",
children: /* @__PURE__ */ jsx(Skeleton, { className: "w-full h-full" })
}, i))
})]
}), /* @__PURE__ */ jsxs("div", {
className: "space-y-6",
children: [
/* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-3/4 mb-4" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-1/3 mb-2" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-1/4" })
] }),
/* @__PURE__ */ jsxs("div", {
className: "space-y-2 border-t border-border pt-4",
children: [
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-3/4" })
]
}),
/* @__PURE__ */ jsx(Skeleton, { className: "h-11 w-full rounded-md" }),
/* @__PURE__ */ jsxs("div", {
className: "border-t border-border pt-4 space-y-2",
children: [/* @__PURE__ */ jsxs("div", {
className: "flex justify-between",
children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-24" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-32" })]
}), /* @__PURE__ */ jsxs("div", {
className: "flex justify-between",
children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-24" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-40" })]
})]
})
]
})]
});
}
function ProductDetails({ productId, storeId: storeIdProp, clientId: clientIdProp, onAddToCartSuccess, onAddToCartError }) {
const context = useGoDaddyContext();
const { t } = context;
const formatCurrency = useFormatCurrency();
const storeId = storeIdProp || context.storeId;
const clientId = clientIdProp || context.clientId;
const [quantity, setQuantity] = useState(1);
const [carouselApi, setCarouselApi] = useState();
const [thumbnailApi, setThumbnailApi] = useState();
const [currentImageIndex, setCurrentImageIndex] = useState(0);
const [variantParams, setVariantParamsState] = useState(() => {
if (typeof window === "undefined") return {};
const params = new URLSearchParams(window.location.search);
const result = {};
params.forEach((value, key) => {
result[key] = value;
});
return result;
});
const { addToCart, isLoading: isAddingToCart } = useAddToCart({
onSuccess: () => {
setQuantity(1);
onAddToCartSuccess?.();
},
onError: onAddToCartError
});
const setVariantParams = useCallback((updates) => {
const params = new URLSearchParams(window.location.search);
Object.entries(updates).forEach(([key, value]) => {
if (value) params.set(key, value);
else params.delete(key);
});
const newUrl = `${window.location.pathname}?${params.toString()}`;
window.history.replaceState({}, "", newUrl);
setVariantParamsState((prev) => {
const next = { ...prev };
Object.entries(updates).forEach(([key, value]) => {
if (value) next[key] = value;
else delete next[key];
});
return next;
});
}, []);
useEffect(() => {
const handlePopState = () => {
const params = new URLSearchParams(window.location.search);
const result = {};
params.forEach((value, key) => {
result[key] = value;
});
setVariantParamsState(result);
};
window.addEventListener("popstate", handlePopState);
return () => window.removeEventListener("popstate", handlePopState);
}, []);
const selectedAttributes = useMemo(() => {
const attrs = {};
Object.entries(variantParams).forEach(([key, value]) => {
if (value) attrs[key] = value;
});
return attrs;
}, [variantParams]);
const selectedAttributeValues = useMemo(() => {
return Object.values(selectedAttributes).filter(Boolean);
}, [selectedAttributes]);
const { data, isLoading, error } = useQuery({
queryKey: [
"sku-group",
storeId,
clientId,
productId,
...selectedAttributeValues.sort()
],
queryFn: () => getSkuGroup({
id: productId,
attributeValues: selectedAttributeValues,
...!selectedAttributeValues.length ? { first: 2 } : {}
}, storeId, clientId, context?.apiHost),
enabled: !!storeId && !!clientId && !!productId,
placeholderData: (previousData) => previousData
});
const attributesEdges = data?.skuGroup?.attributes?.edges || [];
const attributes = useMemo(() => {
return attributesEdges.map((edge) => {
const attributeNode = edge?.node;
const valuesEdges = attributeNode?.values?.edges || [];
return {
id: attributeNode?.id || "",
name: attributeNode?.name || "",
label: attributeNode?.label || attributeNode?.name || "",
values: valuesEdges.map((valueEdge) => {
const valueNode = valueEdge?.node;
return {
id: valueNode?.id || "",
name: valueNode?.name || "",
label: valueNode?.label || valueNode?.name || ""
};
})
};
});
}, [attributesEdges]);
const matchedSkus = data?.skuGroup?.skus?.edges || [];
const matchedSkuId = matchedSkus.length === 1 ? matchedSkus[0]?.node?.id : null;
const { data: individualSkuData, isLoading: isSkuLoading } = useQuery({
queryKey: [
"individual-sku",
storeId,
clientId,
matchedSkuId
],
queryFn: () => getSku({ id: matchedSkuId }, storeId, clientId, context?.apiHost),
enabled: !!storeId && !!clientId && !!matchedSkuId
});
const selectedSku = individualSkuData?.sku;
useEffect(() => {
if (!carouselApi) return;
const onSelect = () => {
const index = carouselApi.selectedScrollSnap();
setCurrentImageIndex(index);
if (thumbnailApi) thumbnailApi.scrollTo(index);
};
onSelect();
carouselApi.on("select", onSelect);
return () => {
carouselApi.off("select", onSelect);
};
}, [carouselApi, thumbnailApi]);
if (isLoading) return /* @__PURE__ */ jsx(ProductDetailsSkeleton, {});
if (error) return /* @__PURE__ */ jsx("div", {
className: "p-4",
children: /* @__PURE__ */ jsxs("div", {
className: "text-center text-destructive",
children: [
t.storefront.errorLoadingProduct,
" ",
error.message
]
})
});
const product = data?.skuGroup;
if (!product) return /* @__PURE__ */ jsx("div", {
className: "p-4",
children: /* @__PURE__ */ jsx("div", {
className: "text-center text-muted-foreground",
children: t.storefront.productNotFound
})
});
const title = product?.label || product?.name || t.storefront.product;
const description = product?.description || "";
const htmlDescription = product?.htmlDescription || "";
const skuPrice = selectedSku?.prices?.edges?.[0]?.node;
const priceMin = skuPrice?.value?.value ?? product?.priceRange?.min ?? 0;
const priceMax = selectedSku ? priceMin : product?.priceRange?.max ?? priceMin;
const compareAtMin = skuPrice?.compareAtValue?.value ?? product?.compareAtPriceRange?.min;
const compareAtMax = selectedSku ? compareAtMin : product?.compareAtPriceRange?.max;
const isOnSale = compareAtMin && compareAtMin > priceMin;
const isPriceRange = priceMin !== priceMax;
const isCompareAtPriceRange = compareAtMin && compareAtMax && compareAtMin !== compareAtMax;
const skuMediaObjects = selectedSku?.mediaObjects?.edges || [];
const productMediaObjects = product?.mediaObjects?.edges || [];
const images = (skuMediaObjects.length > 0 ? skuMediaObjects : productMediaObjects).filter((edge) => edge?.node?.type === "IMAGE").map((edge) => edge?.node?.url).filter(Boolean);
const handleAttributeChange = (attributeName, valueName) => {
setVariantParams({ [attributeName]: valueName });
};
const handleQuantityChange = (change) => {
setQuantity((prev) => Math.max(1, prev + change));
};
const handleThumbnailClick = (index) => {
carouselApi?.scrollTo(index);
thumbnailApi?.scrollTo(index);
};
const isOutOfStock = selectedSku ? (() => {
return (selectedSku.inventoryCounts?.edges?.find((edge) => edge?.node?.type === "AVAILABLE")?.node?.quantity ?? 0) === 0;
})() : false;
const canAddToCart = !isOutOfStock && Boolean(selectedSku);
const handleAddToCart = async () => {
if (!canAddToCart) return;
await addToCart({
skuId: selectedSku?.id || product?.skus?.edges?.[0]?.node?.id || "",
name: title,
quantity,
productAssetUrl: images[0] || void 0
});
};
return /* @__PURE__ */ jsxs("div", {
className: "grid md:grid-cols-2 gap-8 p-4",
children: [/* @__PURE__ */ jsxs("div", {
className: "space-y-4",
children: [/* @__PURE__ */ jsxs("div", {
className: "relative",
children: [isOnSale && /* @__PURE__ */ jsx(Badge, {
variant: "accent",
className: "absolute top-4 right-4 z-10 font-semibold",
children: t.storefront.sale
}), /* @__PURE__ */ jsxs(Carousel, {
setApi: setCarouselApi,
opts: {
align: "center",
loop: true
},
className: "w-full",
children: [/* @__PURE__ */ jsx(CarouselContent, { children: images.length > 0 ? images.map((image, index) => /* @__PURE__ */ jsx(CarouselItem, { children: /* @__PURE__ */ jsx(Card, {
className: "overflow-hidden aspect-square bg-muted border-border",
children: /* @__PURE__ */ jsx("img", {
src: image,
alt: `${title} - ${index + 1}`,
className: "w-full h-full object-cover"
})
}) }, index)) : /* @__PURE__ */ jsx(CarouselItem, { children: /* @__PURE__ */ jsx(Card, {
className: "overflow-hidden aspect-square bg-muted",
children: /* @__PURE__ */ jsx("div", {
className: "w-full h-full flex items-center justify-center text-muted-foreground",
children: t.storefront.noImageAvailable
})
}) }) }), images.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(CarouselPrevious, { className: "left-4" }), /* @__PURE__ */ jsx(CarouselNext, { className: "right-4" })] })]
})]
}), images.length > 1 && /* @__PURE__ */ jsx(Fragment, { children: images.length <= 4 ? /* @__PURE__ */ jsx("div", {
className: "grid grid-cols-4 gap-2",
children: images.map((image, index) => /* @__PURE__ */ jsx(Button, {
variant: "outline",
className: `p-0 h-auto aspect-square overflow-hidden ${currentImageIndex === index ? "ring-2 ring-primary ring-offset-2" : "opacity-60 hover:opacity-100"}`,
onClick: () => handleThumbnailClick(index),
children: /* @__PURE__ */ jsx("img", {
src: image,
alt: `${title} - thumbnail ${index + 1}`,
className: "w-full h-full object-cover"
})
}, index))
}) : /* @__PURE__ */ jsxs(Carousel, {
setApi: setThumbnailApi,
opts: {
align: "start",
slidesToScroll: 1,
containScroll: "keepSnaps"
},
className: "w-full",
children: [
/* @__PURE__ */ jsx(CarouselContent, {
className: "-ml-2",
children: images.map((image, index) => /* @__PURE__ */ jsx(CarouselItem, {
className: "pl-2 basis-1/4",
children: /* @__PURE__ */ jsx("div", {
className: "p-1",
children: /* @__PURE__ */ jsx(Button, {
variant: "outline",
className: `p-0 h-auto aspect-square overflow-hidden w-full ${currentImageIndex === index ? "ring-2 ring-primary ring-offset-2" : "opacity-60 hover:opacity-100"}`,
onClick: () => handleThumbnailClick(index),
children: /* @__PURE__ */ jsx("img", {
src: image,
alt: `${title} - thumbnail ${index + 1}`,
className: "w-full h-full object-cover"
})
})
})
}, index))
}),
/* @__PURE__ */ jsx(CarouselPrevious, { className: "-left-4" }),
/* @__PURE__ */ jsx(CarouselNext, { className: "-right-4" })
]
}) })]
}), /* @__PURE__ */ jsxs("div", {
className: "space-y-6",
children: [
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
className: "text-3xl font-bold text-foreground mb-2",
children: title
}), /* @__PURE__ */ jsxs("div", {
className: "flex items-baseline gap-3 mb-4",
children: [/* @__PURE__ */ jsx("span", {
className: "text-2xl font-bold text-foreground",
children: isPriceRange ? `${formatCurrency({
amount: priceMin,
currencyCode: "USD",
inputInMinorUnits: true
})} - ${formatCurrency({
amount: priceMax,
currencyCode: "USD",
inputInMinorUnits: true
})}` : formatCurrency({
amount: priceMin,
currencyCode: "USD",
inputInMinorUnits: true
})
}), isOnSale && compareAtMin && /* @__PURE__ */ jsx("span", {
className: "text-lg text-muted-foreground line-through",
children: isCompareAtPriceRange ? `${formatCurrency({
amount: compareAtMin,
currencyCode: "USD",
inputInMinorUnits: true
})} - ${formatCurrency({
amount: compareAtMax,
currencyCode: "USD",
inputInMinorUnits: true
})}` : formatCurrency({
amount: compareAtMin,
currencyCode: "USD",
inputInMinorUnits: true
})
})]
})] }),
htmlDescription || description ? /* @__PURE__ */ jsx("div", { children: htmlDescription ? /* @__PURE__ */ jsx("div", {
className: "text-muted-foreground prose prose-sm max-w-none",
dangerouslySetInnerHTML: { __html: htmlDescription }
}) : /* @__PURE__ */ jsx("p", {
className: "text-muted-foreground",
children: description
}) }) : null,
attributes.length > 0 && /* @__PURE__ */ jsxs("div", {
className: "space-y-4",
children: [attributes.map((attribute) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
className: "text-sm font-medium text-foreground mb-2 block",
children: attribute.label
}), /* @__PURE__ */ jsx("div", {
className: "flex flex-wrap gap-2",
children: attribute.values.map((value) => /* @__PURE__ */ jsx(Button, {
variant: selectedAttributes[attribute.name] === value.name ? "default" : "outline",
size: "sm",
onClick: () => handleAttributeChange(attribute.name, value.name),
className: "min-w-[60px]",
children: value.label
}, value.id))
})] }, attribute.id)), selectedAttributeValues.length > 0 && /* @__PURE__ */ jsxs("div", {
className: "text-sm",
children: [
isSkuLoading && /* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-2 text-muted-foreground",
children: [/* @__PURE__ */ jsx("div", { className: "h-4 w-4 animate-spin rounded-full border-2 border-muted-foreground border-t-transparent" }), t.storefront.loadingVariantDetails]
}),
!isSkuLoading && matchedSkus.length === 0 && /* @__PURE__ */ jsx("div", {
className: "text-destructive",
children: t.storefront.combinationNotAvailable
}),
!isSkuLoading && matchedSkus.length > 1 && /* @__PURE__ */ jsxs("div", {
className: "text-muted-foreground",
children: [
matchedSkus.length,
" ",
t.storefront.variantsMatch
]
})
]
})]
}),
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
className: "text-sm font-medium text-foreground mb-2 block",
children: t.storefront.quantity
}), /* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-3",
children: [
/* @__PURE__ */ jsx(Button, {
variant: "outline",
size: "icon",
onClick: () => handleQuantityChange(-1),
disabled: quantity <= 1,
children: /* @__PURE__ */ jsx(Minus, { className: "h-4 w-4" })
}),
/* @__PURE__ */ jsx("span", {
className: "text-lg font-medium min-w-[40px] text-center",
children: quantity
}),
/* @__PURE__ */ jsx(Button, {
variant: "outline",
size: "icon",
onClick: () => handleQuantityChange(1),
children: /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" })
})
]
})] }),
/* @__PURE__ */ jsx(Button, {
size: "lg",
className: "w-full gap-2",
onClick: handleAddToCart,
disabled: !canAddToCart || isAddingToCart,
children: isAddingToCart ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Loader2, { className: "h-5 w-5 animate-spin" }), t.storefront.addingToCart] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ShoppingCart, { className: "h-5 w-5" }), isOutOfStock ? t.storefront.outOfStock : t.storefront.addToCart] })
}),
/* @__PURE__ */ jsxs("div", {
className: "border-t border-border pt-4 space-y-2",
children: [
product?.type && /* @__PURE__ */ jsxs("div", {
className: "flex justify-between text-sm",
children: [/* @__PURE__ */ jsx("span", {
className: "text-muted-foreground",
children: t.storefront.productType
}), /* @__PURE__ */ jsx("span", {
className: "font-medium text-foreground",
children: product.type
})]
}),
product?.id && /* @__PURE__ */ jsxs("div", {
className: "flex justify-between text-sm",
children: [/* @__PURE__ */ jsx("span", {
className: "text-muted-foreground",
children: t.storefront.productId
}), /* @__PURE__ */ jsx("span", {
className: "font-mono text-xs text-foreground",
children: product.id
})]
}),
selectedSku && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
className: "flex justify-between text-sm",
children: [/* @__PURE__ */ jsx("span", {
className: "text-muted-foreground",
children: t.storefront.selectedSku
}), /* @__PURE__ */ jsx("span", {
className: "font-mono text-xs text-foreground",
children: selectedSku.code
})]
}), selectedSku.inventoryCounts?.edges && selectedSku.inventoryCounts.edges.length > 0 && /* @__PURE__ */ jsxs("div", {
className: "flex justify-between text-sm",
children: [/* @__PURE__ */ jsx("span", {
className: "text-muted-foreground",
children: t.storefront.stockStatus
}), /* @__PURE__ */ jsx("span", {
className: "font-medium text-foreground",
children: (() => {
const availableCount = selectedSku.inventoryCounts.edges.find((edge) => edge?.node?.type === "AVAILABLE")?.node?.quantity ?? 0;
if (availableCount === 0) return t.storefront.outOfStock;
if (availableCount < 10) return `${t.storefront.lowStock} (${availabl