storefront-layouts 1.0.9 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/dist/components/shipping/ProductDeliveryInfoSection.d.ts.map +1 -1
  2. package/dist/components/shipping/ProductDeliveryInfoSection.js +10 -3
  3. package/dist/components/ui/address-selector.d.ts.map +1 -1
  4. package/dist/components/ui/address-selector.js +4 -3
  5. package/dist/components/ui/button.js +1 -1
  6. package/dist/components/ui/checkout-button.d.ts.map +1 -1
  7. package/dist/components/ui/checkout-button.js +2 -1
  8. package/dist/components/ui/product-card.d.ts.map +1 -1
  9. package/dist/components/ui/product-card.js +15 -4
  10. package/dist/components/ui/route-skeleton-loader.d.ts.map +1 -1
  11. package/dist/components/ui/route-skeleton-loader.js +6 -2
  12. package/dist/components/ui/sheet.d.ts.map +1 -1
  13. package/dist/components/ui/sheet.js +2 -2
  14. package/dist/components/ui/skeletons/checkout-skeleton.js +1 -1
  15. package/dist/editor/blocks/defaults/clothing-minimal-home.js +1 -1
  16. package/dist/editor/blocks/migrate-clothing-minimal.js +1 -1
  17. package/dist/hooks/use-pdp-variation.d.ts +57 -0
  18. package/dist/hooks/use-pdp-variation.d.ts.map +1 -0
  19. package/dist/hooks/use-pdp-variation.js +69 -0
  20. package/dist/hooks/use-product-by-slug.d.ts.map +1 -1
  21. package/dist/hooks/use-product-by-slug.js +0 -5
  22. package/dist/hooks/use-product-variation-selection.js +1 -1
  23. package/dist/hooks/use-shipping-quote.d.ts +1 -0
  24. package/dist/hooks/use-shipping-quote.d.ts.map +1 -1
  25. package/dist/hooks/use-shipping-quote.js +3 -0
  26. package/dist/layouts/booking/pages/ProductDetailPage.d.ts.map +1 -1
  27. package/dist/layouts/booking/pages/ProductDetailPage.js +130 -223
  28. package/dist/layouts/booking-agenda/pages/ProductDetailPage.d.ts.map +1 -1
  29. package/dist/layouts/booking-agenda/pages/ProductDetailPage.js +130 -223
  30. package/dist/layouts/clothing/components/ClothingHomePage.d.ts.map +1 -1
  31. package/dist/layouts/clothing/components/ClothingHomePage.js +40 -2
  32. package/dist/layouts/clothing/pages/ProductDetailPage.d.ts.map +1 -1
  33. package/dist/layouts/clothing/pages/ProductDetailPage.js +127 -209
  34. package/dist/layouts/clothing/pages/ProductsPage.d.ts.map +1 -1
  35. package/dist/layouts/clothing/pages/ProductsPage.js +2 -5
  36. package/dist/layouts/clothing-minimal/components/ClothingHomePageMinimal.d.ts.map +1 -1
  37. package/dist/layouts/clothing-minimal/components/ClothingHomePageMinimal.js +5 -3
  38. package/dist/layouts/clothing-minimal/components/clothing-minimal-block-render.d.ts.map +1 -1
  39. package/dist/layouts/clothing-minimal/components/clothing-minimal-block-render.js +13 -4
  40. package/dist/layouts/clothing-minimal/pages/ProductDetailPage.d.ts.map +1 -1
  41. package/dist/layouts/clothing-minimal/pages/ProductDetailPage.js +130 -223
  42. package/dist/layouts/clothing-minimal/pages/ProductsPage.d.ts.map +1 -1
  43. package/dist/layouts/clothing-minimal/pages/ProductsPage.js +2 -5
  44. package/dist/layouts/electronics/components/ElectronicsGridProductCard.js +1 -1
  45. package/dist/layouts/electronics/components/ElectronicsStoreHeader.d.ts.map +1 -1
  46. package/dist/layouts/electronics/components/ElectronicsStoreHeader.js +10 -9
  47. package/dist/layouts/electronics/pages/ProductDetailPage.d.ts.map +1 -1
  48. package/dist/layouts/electronics/pages/ProductDetailPage.js +130 -223
  49. package/dist/layouts/electronics/pages/ProductsPage.d.ts.map +1 -1
  50. package/dist/layouts/electronics/pages/ProductsPage.js +2 -5
  51. package/dist/layouts/electronics-grid/components/ElectronicsGridProductCard.js +1 -1
  52. package/dist/layouts/electronics-grid/components/ElectronicsHomePageGrid.d.ts.map +1 -1
  53. package/dist/layouts/electronics-grid/components/ElectronicsHomePageGrid.js +4 -5
  54. package/dist/layouts/electronics-grid/pages/ProductDetailPage.d.ts.map +1 -1
  55. package/dist/layouts/electronics-grid/pages/ProductDetailPage.js +130 -223
  56. package/dist/layouts/electronics-grid/pages/ProductsPage.d.ts.map +1 -1
  57. package/dist/layouts/electronics-grid/pages/ProductsPage.js +2 -5
  58. package/dist/layouts/food/pages/MenuPage.js +1 -1
  59. package/dist/layouts/food/pages/ProductDetailPage.d.ts.map +1 -1
  60. package/dist/layouts/food/pages/ProductDetailPage.js +37 -140
  61. package/dist/layouts/food/pages/ProductsPage.d.ts.map +1 -1
  62. package/dist/layouts/food/pages/ProductsPage.js +2 -5
  63. package/dist/layouts/food-modern/pages/MenuPage.js +1 -1
  64. package/dist/layouts/food-modern/pages/ProductDetailPage.d.ts.map +1 -1
  65. package/dist/layouts/food-modern/pages/ProductDetailPage.js +130 -223
  66. package/dist/layouts/food-modern/pages/ProductsPage.d.ts.map +1 -1
  67. package/dist/layouts/food-modern/pages/ProductsPage.js +2 -5
  68. package/dist/layouts/motivational-speaker/pages/ProductsPage.d.ts.map +1 -1
  69. package/dist/layouts/motivational-speaker/pages/ProductsPage.js +2 -5
  70. package/dist/layouts/shared/components/PdpColorSwatch.d.ts +11 -0
  71. package/dist/layouts/shared/components/PdpColorSwatch.d.ts.map +1 -0
  72. package/dist/layouts/shared/components/PdpColorSwatch.js +29 -0
  73. package/dist/layouts/shared/components/PdpVariationSelectors.d.ts +13 -0
  74. package/dist/layouts/shared/components/PdpVariationSelectors.d.ts.map +1 -0
  75. package/dist/layouts/shared/components/PdpVariationSelectors.js +65 -0
  76. package/dist/layouts/shared/components/StoreCartSheet.d.ts +8 -0
  77. package/dist/layouts/shared/components/StoreCartSheet.d.ts.map +1 -0
  78. package/dist/layouts/shared/components/StoreCartSheet.js +16 -0
  79. package/dist/layouts/shared/components/StoreHeader.d.ts.map +1 -1
  80. package/dist/layouts/shared/components/StoreHeader.js +13 -12
  81. package/dist/layouts/shared/components/headers/ClothingMinimalHeader.d.ts.map +1 -1
  82. package/dist/layouts/shared/components/headers/ClothingMinimalHeader.js +5 -7
  83. package/dist/layouts/shared/pages/CartPage.d.ts.map +1 -1
  84. package/dist/layouts/shared/pages/CartPage.js +6 -5
  85. package/dist/layouts/shared/pages/CheckoutPage.d.ts.map +1 -1
  86. package/dist/layouts/shared/pages/CheckoutPage.js +209 -107
  87. package/dist/layouts/shared/product-category-filter-chip.d.ts +2 -0
  88. package/dist/layouts/shared/product-category-filter-chip.d.ts.map +1 -0
  89. package/dist/layouts/shared/product-category-filter-chip.js +6 -0
  90. package/dist/lib/pdp-cart-actions.d.ts +5 -0
  91. package/dist/lib/pdp-cart-actions.d.ts.map +1 -0
  92. package/dist/lib/pdp-cart-actions.js +19 -0
  93. package/dist/lib/product-variation-utils.d.ts +17 -0
  94. package/dist/lib/product-variation-utils.d.ts.map +1 -1
  95. package/dist/lib/product-variation-utils.js +193 -14
  96. package/dist/lib/services/checkout.service.d.ts +11 -0
  97. package/dist/lib/services/checkout.service.d.ts.map +1 -1
  98. package/dist/lib/services/shipping.service.d.ts +3 -0
  99. package/dist/lib/services/shipping.service.d.ts.map +1 -1
  100. package/dist/lib/store-config-utils.d.ts.map +1 -1
  101. package/dist/lib/store-config-utils.js +104 -11
  102. package/dist/lib/store-context.d.ts.map +1 -1
  103. package/dist/lib/store-context.js +23 -8
  104. package/dist/lib/utils/asset-helpers.d.ts +2 -0
  105. package/dist/lib/utils/asset-helpers.d.ts.map +1 -1
  106. package/dist/lib/utils/asset-helpers.js +24 -0
  107. package/dist/styles/index.css +143 -45
  108. package/package.json +8 -2
@@ -5,7 +5,7 @@ import { useProductReviews } from '../../../hooks/use-product-reviews.js';
5
5
  import { Button } from '../../../components/ui/button.js';
6
6
  import { Breadcrumbs } from '../../../components/ui/breadcrumbs.js';
7
7
  import { Modal } from '../../../components/ui/modal.js';
8
- import { ShoppingCart, Heart, Star, CheckCircle, ThumbsUp, User, Minus, Plus, Ruler, Share2, Info } from 'lucide-react';
8
+ import { ShoppingCart, Heart, Star, CheckCircle, ThumbsUp, User, Minus, Plus, Share2, Info } from 'lucide-react';
9
9
  import { useState, useMemo, useEffect } from 'react';
10
10
  import { useStore } from '../../../lib/store-context.js';
11
11
  import { useToast } from '../../../components/ui/toast.js';
@@ -23,9 +23,13 @@ import { ButtonLoader } from '../../../components/ui/page-loader.js';
23
23
  import { extractImageUrls } from '../../../lib/store-config-utils.js';
24
24
  import { OutOfStockOverlay } from '../../../components/ui/out-of-stock-overlay.js';
25
25
  import { cn, formatCurrency, filterActiveProducts, isEditorPreviewMode, resolvePdpProduct } from '../../../lib/utils/index.js';
26
+ import { usePdpVariation } from '../../../hooks/use-pdp-variation.js';
27
+ import { buildPdpBuyNowItem } from '../../../lib/pdp-cart-actions.js';
28
+ import { assertVariationSelected } from '../../../lib/product-variation-utils.js';
26
29
  import { shippingService } from '../../../lib/services/shipping.service.js';
27
30
  import { locationService } from '../../../lib/services/location.service';
28
31
  import { ProductDeliveryInfoSection } from '../../../components/shipping/ProductDeliveryInfoSection';
32
+ import { PdpVariationSelectors } from '../../shared/components/PdpVariationSelectors';
29
33
  import { formatDateSafe } from '../../../utils/date';
30
34
  // Helper function to format date
31
35
  const formatDate = (dateString) => {
@@ -117,21 +121,11 @@ export function ProductDetailPage({ storeConfig: initialConfig, productSlug }) {
117
121
  addToast('Failed to update wishlist', 'error');
118
122
  }
119
123
  };
120
- // Extract color and size variants
121
- const colorVariants = product?.variants?.filter(v => v.name === 'Color') || [];
122
- const sizeVariants = product?.variants?.filter(v => v.name === 'Size') || [];
123
- // Initialize state with defaults
124
+ const { optionGroups, selectedAttributes, setAttribute, isOptionAvailable, selectedVariation, galleryImages, displayPrice, displayCompareAt, isOutOfStock, getOptionImage, } = usePdpVariation(product);
124
125
  const [selectedImage, setSelectedImage] = useState(0);
125
- // Ensure selectedImage is within bounds when images change
126
126
  useEffect(() => {
127
- if (product && product.images && product.images.length > 0 && selectedImage >= product.images.length) {
128
- // Use setTimeout to avoid synchronous setState in effect
129
- const timer = setTimeout(() => setSelectedImage(0), 0);
130
- return () => clearTimeout(timer);
131
- }
132
- }, [product, selectedImage]);
133
- const [selectedColor, setSelectedColor] = useState('');
134
- const [selectedSize, setSelectedSize] = useState('');
127
+ setSelectedImage(0);
128
+ }, [selectedVariation?.id, galleryImages.join('|')]);
135
129
  // Pickup/Delivery state
136
130
  const [isPickup, setIsPickup] = useState(true); // Default to pickup
137
131
  const [locations, setLocations] = useState([]);
@@ -159,13 +153,6 @@ export function ProductDetailPage({ storeConfig: initialConfig, productSlug }) {
159
153
  };
160
154
  fetchLocations();
161
155
  }, [storeConfig?.id]);
162
- useEffect(() => {
163
- if (product && product.images && product.images.length > 0 && selectedImage >= product.images.length) {
164
- // Use setTimeout to avoid synchronous setState in effect
165
- const timer = setTimeout(() => setSelectedImage(0), 0);
166
- return () => clearTimeout(timer);
167
- }
168
- }, [product, selectedImage]);
169
156
  const [quantity, setQuantity] = useState(1);
170
157
  const [showAllReviews, setShowAllReviews] = useState(false);
171
158
  const [isReviewModalOpen, setIsReviewModalOpen] = useState(false);
@@ -259,42 +246,7 @@ export function ProductDetailPage({ storeConfig: initialConfig, productSlug }) {
259
246
  // Extract product images - product.images should already be string[] from transformation,
260
247
  // but handle edge cases where it might still be in object format
261
248
  // Use the comprehensive extraction utility from store-config-utils for consistency
262
- const productImages = (() => {
263
- // Debug: Log the raw product.images to understand its format
264
- if (process.env.NODE_ENV === 'development') {
265
- console.log('[ProductDetailPage] Raw product.images:', {
266
- productId: product.id,
267
- productName: product.name,
268
- images: product.images,
269
- imagesType: typeof product.images,
270
- imagesIsArray: Array.isArray(product.images),
271
- imagesLength: Array.isArray(product.images) ? product.images.length : 'N/A',
272
- firstItem: Array.isArray(product.images) && product.images.length > 0 ? product.images[0] : 'N/A',
273
- firstItemType: Array.isArray(product.images) && product.images.length > 0 ? typeof product.images[0] : 'N/A'
274
- });
275
- }
276
- // If already an array of strings (normal case after transformation)
277
- if (Array.isArray(product.images) && product.images.length > 0) {
278
- const firstItem = product.images[0];
279
- if (typeof firstItem === 'string') {
280
- const filtered = product.images.filter((img) => typeof img === 'string' && img.trim().length > 0);
281
- if (process.env.NODE_ENV === 'development') {
282
- console.log('[ProductDetailPage] Using string array directly:', { count: filtered.length, firstImage: filtered[0]?.substring(0, 100) });
283
- }
284
- return filtered;
285
- }
286
- // If array but not strings, extract using utility
287
- if (process.env.NODE_ENV === 'development') {
288
- console.log('[ProductDetailPage] Array but not strings, using extractImageUrls');
289
- }
290
- }
291
- // Use the comprehensive extraction utility for any other format (objects, nested structures, etc.)
292
- const extracted = extractImageUrls(product.images);
293
- if (process.env.NODE_ENV === 'development') {
294
- console.log('[ProductDetailPage] Extracted using utility:', { count: extracted.length, firstImage: extracted[0]?.substring(0, 100) });
295
- }
296
- return extracted;
297
- })();
249
+ const productImages = galleryImages.length > 0 ? galleryImages : extractImageUrls(product.images);
298
250
  // For gallery, use actual images only (don't duplicate to fill 5 slots)
299
251
  const displayImages = productImages.slice(0, 5);
300
252
  const hasMoreImages = productImages.length > 5;
@@ -319,20 +271,6 @@ export function ProductDetailPage({ storeConfig: initialConfig, productSlug }) {
319
271
  });
320
272
  }
321
273
  }
322
- // Color swatch mapping
323
- const colorSwatches = {
324
- 'White': '#FFFFFF',
325
- 'Black': '#000000',
326
- 'Gray': '#808080',
327
- 'Grey': '#808080',
328
- 'Blue': '#3B82F6',
329
- 'Red': '#EF4444',
330
- 'Green': '#10B981',
331
- 'Yellow': '#FBBF24',
332
- 'Brown': '#92400E',
333
- 'Navy': '#1E3A8A',
334
- 'Beige': '#F5F5DC',
335
- };
336
274
  // Reviews loaded from API (product payload does not include review rows)
337
275
  const displayedReviews = showAllReviews ? reviews : reviews.slice(0, 3);
338
276
  const hasMoreReviews = reviews.length > 3;
@@ -341,7 +279,6 @@ export function ProductDetailPage({ storeConfig: initialConfig, productSlug }) {
341
279
  ? reviews.reduce((sum, review) => sum + review.rating, 0) / reviews.length
342
280
  : Number(product.rating) || 0;
343
281
  // Out of stock check
344
- const isOutOfStock = !product.inStock;
345
282
  // Calculate rating distribution
346
283
  const ratingDistribution = [5, 4, 3, 2, 1].map(rating => {
347
284
  const count = reviews.filter(r => Math.round(r.rating) === rating).length;
@@ -351,29 +288,19 @@ export function ProductDetailPage({ storeConfig: initialConfig, productSlug }) {
351
288
  percentage: reviews.length > 0 ? (count / reviews.length) * 100 : 0,
352
289
  };
353
290
  });
354
- const handleAddToCart = () => {
291
+ const handleAddToCart = async () => {
355
292
  if (isOutOfStock) {
356
293
  addToast('This product is currently out of stock', 'error');
357
294
  return;
358
295
  }
359
- // Find the matching variation UUID based on selected attributes
360
- // Note: StoreProduct uses 'variants' not 'variations'
361
- let variationId;
362
- if (product.variants && Array.isArray(product.variants) && product.variants.length > 0) {
363
- const matchingVariation = product.variants.find((variation) => {
364
- const attrs = variation.attributes || {};
365
- const colorMatch = !selectedColor || attrs.color === selectedColor;
366
- const sizeMatch = !selectedSize || attrs.size === selectedSize;
367
- return colorMatch && sizeMatch;
368
- });
369
- variationId = matchingVariation?.id;
296
+ try {
297
+ assertVariationSelected(product, selectedVariation, selectedAttributes);
298
+ await addToCart(product, quantity, selectedVariation?.id);
299
+ addToast(`${product.name} added to cart`, 'success');
300
+ }
301
+ catch (error) {
302
+ addToast(error instanceof Error ? error.message : 'Could not add to cart', 'error');
370
303
  }
371
- // Fallback to composite string for backward compatibility if no variation found
372
- const variantId = variationId || (selectedColor || selectedSize
373
- ? [selectedColor, selectedSize].filter(Boolean).join(' / ')
374
- : undefined);
375
- addToCart(product, quantity, variantId);
376
- addToast(`${product.name} added to cart`, 'success');
377
304
  };
378
305
  const handleBuyNow = async () => {
379
306
  if (isOutOfStock) {
@@ -385,43 +312,13 @@ export function ProductDetailPage({ storeConfig: initialConfig, productSlug }) {
385
312
  }
386
313
  setIsBuyNowLoading(true);
387
314
  try {
388
- // Find the matching variation UUID based on selected attributes
389
- // Note: StoreProduct uses 'variants' not 'variations'
390
- let variationId;
391
- let variantName;
392
- if (product.variants && Array.isArray(product.variants) && product.variants.length > 0) {
393
- const matchingVariation = product.variants.find((variation) => {
394
- const attrs = variation.attributes || {};
395
- const colorMatch = !selectedColor || attrs.color === selectedColor;
396
- const sizeMatch = !selectedSize || attrs.size === selectedSize;
397
- return colorMatch && sizeMatch;
398
- });
399
- variationId = matchingVariation?.id;
400
- variantName = matchingVariation?.name || matchingVariation?.value;
401
- }
402
- // Fallback to composite string for backward compatibility if no variation found
403
- const variantId = variationId || (selectedColor || selectedSize
404
- ? [selectedColor, selectedSize].filter(Boolean).join(' / ')
405
- : undefined);
406
- // Create buy now item (don't add to cart)
407
- const buyNowItemId = variantId ? `${product.id}-${variantId}` : product.id;
408
- const buyNowItem = {
409
- id: buyNowItemId,
410
- productId: product.id,
411
- quantity,
412
- variantId,
413
- variantName: variantName || variantId,
414
- price: product.price,
415
- product
416
- };
417
- // Set buy now item and navigate directly to checkout
315
+ const buyNowItem = buildPdpBuyNowItem(product, quantity, selectedVariation, displayPrice, selectedAttributes);
418
316
  setBuyNowItem(buyNowItem);
419
- // Navigate to checkout page for direct checkout
420
317
  router.push(`/${storeConfig.slug}/checkout`);
421
318
  }
422
319
  catch (error) {
423
320
  console.error('Buy Now error:', error);
424
- addToast('Failed to proceed to checkout. Please try again.', 'error');
321
+ addToast(error instanceof Error ? error.message : 'Failed to proceed to checkout. Please try again.', 'error');
425
322
  }
426
323
  finally {
427
324
  setIsBuyNowLoading(false);
@@ -444,7 +341,7 @@ export function ProductDetailPage({ storeConfig: initialConfig, productSlug }) {
444
341
  setShippingError(null);
445
342
  try {
446
343
  const currency = product.currency || storeConfig.settings?.currency || 'USD';
447
- const subtotal = product.price * quantity;
344
+ const subtotal = displayPrice * quantity;
448
345
  const result = await shippingService.calculateShipping({
449
346
  storeId: storeConfig.id,
450
347
  address: {
@@ -511,7 +408,7 @@ export function ProductDetailPage({ storeConfig: initialConfig, productSlug }) {
511
408
  }, 500); // Debounce 500ms
512
409
  return () => clearTimeout(timer);
513
410
  // eslint-disable-next-line react-hooks/exhaustive-deps
514
- }, [shippingAddress.country, shippingAddress.city, shippingAddress.zipCode, quantity, product?.id, product?.price]);
411
+ }, [shippingAddress.country, shippingAddress.city, shippingAddress.zipCode, quantity, product?.id, displayPrice]);
515
412
  // Track product view
516
413
  useEffect(() => {
517
414
  if (product && storeConfig?.id) {
@@ -521,26 +418,26 @@ export function ProductDetailPage({ storeConfig: initialConfig, productSlug }) {
521
418
  productId: product.id,
522
419
  productName: product.name,
523
420
  categoryId: product.categoryId,
524
- price: product.price,
421
+ price: displayPrice,
525
422
  },
526
423
  });
527
424
  }
528
- }, [product?.id, storeConfig?.id, trackEvent]);
425
+ }, [product?.id, storeConfig?.id, displayPrice, trackEvent]);
529
426
  return (_jsxs(_Fragment, { children: [_jsx("div", { className: "min-h-screen bg-white", children: _jsxs("div", { "data-content-ready": true, className: "container mx-auto px-4 sm:px-6 lg:px-8 py-8 lg:py-12 max-w-7xl", children: [_jsx("div", { className: "mb-6", children: _jsx(Breadcrumbs, { items: breadcrumbItems }) }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-16 mb-20", children: [_jsxs("div", { className: "relative lg:sticky lg:top-24 self-start z-0", children: [_jsxs("div", { className: "relative aspect-square bg-gray-50 rounded-xl overflow-hidden mb-4 border border-gray-100 shadow-sm group", children: [_jsx(ImageWithFallback, { "data-product-image": true, src: displayImages[selectedImage] || displayImages[0] || productImages[0], alt: product.name, className: cn("w-full h-full object-cover transition-transform duration-300 group-hover:scale-105", isOutOfStock && "grayscale"), skeletonAspectRatio: "square" }), isOutOfStock && (_jsx(OutOfStockOverlay, { badgePosition: "center" }))] }), displayImages.length > 0 && (_jsxs("div", { className: "grid grid-cols-5 gap-2", children: [displayImages.map((image, index) => (_jsx("button", { onClick: () => setSelectedImage(Math.min(index, displayImages.length - 1)), className: `aspect-square rounded-lg overflow-hidden border-2 transition-all ${selectedImage === index
530
427
  ? 'border-gray-900 ring-2 ring-gray-900 ring-offset-2'
531
- : 'border-gray-200 hover:border-gray-300'}`, children: _jsx(ImageWithFallback, { src: image, alt: `${product.name} view ${index + 1}`, className: cn("w-full h-full object-cover", isOutOfStock && "grayscale"), skeletonAspectRatio: "square" }) }, index))), hasMoreImages && (_jsxs("div", { className: "aspect-square rounded-lg border-2 border-gray-200 bg-gray-50 flex items-center justify-center text-xs text-gray-500 font-medium", children: ["+", productImages.length - 5, " more"] }))] }))] }), _jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("span", { className: "text-sm font-semibold text-gray-700 uppercase tracking-wide", children: directCategory?.name || storeConfig.name }), _jsx("button", { onClick: handleShare, className: "text-gray-400 hover:text-gray-900 transition-colors p-2 rounded-full hover:bg-gray-100", title: "Share this product", children: _jsx(Share2, { className: "h-5 w-5" }) })] }), _jsx("h1", { "data-product-name": true, className: "text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 mb-3 leading-tight tracking-tight", children: product.name }), _jsxs("p", { className: "text-sm text-gray-500 mb-6 font-mono", children: ["SKU: ", displaySKU] }), _jsx("div", { className: "mb-4 flex items-center gap-2", children: product.inStock ? (_jsxs("span", { className: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800", children: [_jsx(CheckCircle, { className: "w-3 h-3 mr-1" }), "In Stock"] })) : (_jsxs("span", { className: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800", children: [_jsx(Info, { className: "w-3 h-3 mr-1" }), "Out of Stock"] })) }), _jsxs("div", { className: "mb-8", children: [_jsxs("div", { className: "flex items-baseline gap-4", children: [_jsx("span", { "data-product-price": true, className: "text-4xl lg:text-5xl font-bold text-gray-900", children: formatCurrency(product.price, product.currency || 'USD') }), product.compareAtPrice != null && product.compareAtPrice > product.price && (_jsx("span", { className: "text-2xl text-gray-500 line-through", children: formatCurrency(product.compareAtPrice, product.currency || 'USD') }))] }), product.compareAtPrice != null && product.compareAtPrice > product.price && (_jsxs("span", { className: "inline-block mt-2 bg-red-100 text-red-700 text-xs font-bold px-2 py-1 rounded", children: ["SAVE ", Math.round(((product.compareAtPrice - product.price) / product.compareAtPrice) * 100), "%"] }))] }), colorVariants.length > 0 && (_jsxs("div", { className: "mb-8", children: [_jsxs("div", { className: "flex items-center gap-2 mb-4", children: [_jsx("span", { className: "text-sm font-semibold text-gray-900", children: "Color:" }), _jsx("span", { className: "text-sm text-gray-600 capitalize", children: selectedColor || colorVariants[0]?.value })] }), _jsx("div", { className: "flex gap-3", children: colorVariants.map((variant) => {
532
- const colorValue = variant.value;
533
- const swatchColor = colorSwatches[colorValue] || '#CCCCCC';
534
- const isSelected = selectedColor === colorValue;
535
- return (_jsx("button", { onClick: () => setSelectedColor(colorValue), className: `relative w-12 h-12 rounded-full border-2 transition-all ${isSelected
536
- ? 'border-gray-900 ring-2 ring-gray-900 ring-offset-2 scale-105'
537
- : 'border-gray-200 hover:border-gray-300'}`, style: { backgroundColor: swatchColor }, title: colorValue, children: isSelected && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsx(CheckCircle, { className: `h-5 w-5 ${['White', 'Beige', 'Yellow'].includes(colorValue) ? 'text-gray-900' : 'text-white'} drop-shadow-sm` }) })) }, variant.id));
538
- }) })] })), sizeVariants.length > 0 && (_jsxs("div", { className: "mb-8", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm font-semibold text-gray-900", children: "Size:" }), _jsx("span", { className: "text-sm text-gray-600", children: selectedSize || 'Select Size' })] }), _jsxs("a", { href: `/${storeConfig.slug}/size-guide`, target: "_blank", rel: "noopener noreferrer", className: "flex items-center gap-1.5 text-xs font-medium text-gray-600 hover:text-gray-900 underline decoration-gray-400 underline-offset-4", children: [_jsx(Ruler, { className: "h-3.5 w-3.5" }), " Size guide"] })] }), _jsx("div", { className: "grid grid-cols-4 sm:grid-cols-5 gap-2.5", children: sizeVariants.map((variant) => {
539
- const isSelected = selectedSize === variant.value;
540
- return (_jsx("button", { onClick: () => setSelectedSize(variant.value), className: `px-4 py-3 rounded-lg border-2 text-sm font-semibold transition-all ${isSelected
541
- ? 'bg-gray-900 text-white border-gray-900 shadow-md transform -translate-y-0.5'
542
- : 'bg-white text-gray-700 border-gray-200 hover:border-gray-300 hover:bg-gray-50'}`, children: variant.value }, variant.id));
543
- }) })] })), _jsxs("div", { className: "flex flex-col gap-4 mb-8", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-sm font-semibold text-gray-900", children: "Quantity" }), _jsxs("div", { className: "flex items-center border border-gray-300 rounded-lg", children: [_jsx("button", { className: "p-3 text-gray-600 hover:bg-gray-100 rounded-l-lg disabled:opacity-50", onClick: () => setQuantity(Math.max(1, quantity - 1)), disabled: quantity <= 1, children: _jsx(Minus, { className: "h-4 w-4" }) }), _jsx("span", { className: "w-12 text-center font-semibold text-gray-900", children: quantity }), _jsx("button", { className: "p-3 text-gray-600 hover:bg-gray-100 rounded-r-lg", onClick: () => setQuantity(quantity + 1), children: _jsx(Plus, { className: "h-4 w-4" }) })] })] }), _jsxs("div", { className: "flex flex-col sm:flex-row gap-3 mt-2", children: [_jsx(Button, { "data-add-to-cart": true, size: "lg", onClick: handleAddToCart, disabled: isCartLoading || isOutOfStock, className: "flex-1 bg-white border-2 border-gray-900 text-gray-900 hover:bg-gray-50 rounded-full h-14 text-base font-bold shadow-sm transition-all disabled:opacity-50 disabled:cursor-not-allowed", children: isCartLoading ? (_jsxs(_Fragment, { children: [_jsx(ButtonLoader, { className: "mr-2" }), "Adding..."] })) : (_jsxs(_Fragment, { children: [_jsx(ShoppingCart, { className: "h-5 w-5 mr-2" }), "Add to Cart"] })) }), _jsx(Button, { size: "lg", onClick: handleBuyNow, disabled: isOutOfStock || isBuyNowLoading || isCartLoading, className: "flex-1 bg-gray-900 hover:bg-gray-800 text-white rounded-full h-14 text-base font-bold shadow-lg hover:shadow-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed", children: isBuyNowLoading || isCartLoading ? (_jsxs(_Fragment, { children: [_jsx(ButtonLoader, { className: "mr-2" }), "Adding..."] })) : ('Buy Now') }), storeConfig.features.wishlist && (_jsx(Button, { variant: "outline", size: "lg", onClick: handleWishlistClick, disabled: isWishlistLoading || !product, className: cn("h-14 w-14 rounded-full border-2 transition-all disabled:opacity-50 disabled:cursor-not-allowed", product && isInWishlist(product.id)
428
+ : 'border-gray-200 hover:border-gray-300'}`, children: _jsx(ImageWithFallback, { src: image, alt: `${product.name} view ${index + 1}`, className: cn("w-full h-full object-cover", isOutOfStock && "grayscale"), skeletonAspectRatio: "square" }) }, index))), hasMoreImages && (_jsxs("div", { className: "aspect-square rounded-lg border-2 border-gray-200 bg-gray-50 flex items-center justify-center text-xs text-gray-500 font-medium", children: ["+", productImages.length - 5, " more"] }))] }))] }), _jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("span", { className: "text-sm font-semibold text-gray-700 uppercase tracking-wide", children: directCategory?.name || storeConfig.name }), _jsx("button", { onClick: handleShare, className: "text-gray-400 hover:text-gray-900 transition-colors p-2 rounded-full hover:bg-gray-100", title: "Share this product", children: _jsx(Share2, { className: "h-5 w-5" }) })] }), _jsx("h1", { "data-product-name": true, className: "text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 mb-3 leading-tight tracking-tight", children: product.name }), _jsxs("p", { className: "text-sm text-gray-500 mb-6 font-mono", children: ["SKU: ", displaySKU] }), _jsx("div", { className: "mb-4 flex items-center gap-2", children: !isOutOfStock ? (_jsxs("span", { className: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800", children: [_jsx(CheckCircle, { className: "w-3 h-3 mr-1" }), "In Stock"] })) : (_jsxs("span", { className: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800", children: [_jsx(Info, { className: "w-3 h-3 mr-1" }), "Out of Stock"] })) }), _jsxs("div", { className: "mb-8", children: [_jsxs("div", { className: "flex items-baseline gap-4", children: [_jsx("span", { "data-product-price": true, className: "text-4xl lg:text-5xl font-bold text-gray-900", children: formatCurrency(displayPrice, product.currency || 'USD') }), displayCompareAt != null && displayCompareAt > displayPrice && (_jsx("span", { className: "text-2xl text-gray-500 line-through", children: formatCurrency(displayCompareAt, product.currency || 'USD') }))] }), displayCompareAt != null && displayCompareAt > displayPrice && (_jsxs("span", { className: "inline-block mt-2 bg-red-100 text-red-700 text-xs font-bold px-2 py-1 rounded", children: ["SAVE ", Math.round(((displayCompareAt - displayPrice) / displayCompareAt) * 100), "%"] }))] }), _jsx(PdpVariationSelectors, { optionGroups: optionGroups, selectedAttributes: selectedAttributes, setAttribute: setAttribute, isOptionAvailable: isOptionAvailable, getOptionImage: getOptionImage, storeSlug: storeConfig.slug, showSizeGuide: productDetailConfig?.showSizeGuide !== false, onAfterClearAttribute: () => setQuantity(1) }), _jsxs("div", { className: "flex flex-col gap-4 mb-8", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-sm font-semibold text-gray-900", children: "Quantity" }), _jsxs("div", { className: "flex items-center border border-gray-300 rounded-lg", children: [_jsx("button", { className: "p-3 text-gray-600 hover:bg-gray-100 rounded-l-lg disabled:opacity-50", onClick: () => setQuantity(Math.max(1, quantity - 1)), disabled: quantity <= 1, children: _jsx(Minus, { className: "h-4 w-4" }) }), _jsx("span", { className: "w-12 text-center font-semibold text-gray-900", children: quantity }), _jsx("button", { className: "p-3 text-gray-600 hover:bg-gray-100 rounded-r-lg", onClick: () => setQuantity(quantity + 1), children: _jsx(Plus, { className: "h-4 w-4" }) })] })] }), _jsxs("div", { className: "flex flex-row items-stretch gap-2 sm:gap-3 mt-2 w-full", children: [_jsx(Button, { "data-add-to-cart": true, variant: "outline", size: "lg", onClick: handleAddToCart, disabled: isCartLoading || isOutOfStock, className: "flex-1 min-w-0 bg-white border-2 border-gray-900 text-gray-900 hover:bg-gray-50 rounded-full h-12 sm:h-14 px-3 sm:px-6 text-sm sm:text-base font-bold [&_svg]:text-gray-900 shadow-sm transition-all disabled:opacity-50 disabled:cursor-not-allowed", children: isCartLoading ? (_jsxs(_Fragment, { children: [_jsx(ButtonLoader, { className: "mr-1.5 sm:mr-2" }), "Adding..."] })) : (_jsxs(_Fragment, { children: [_jsx(ShoppingCart, { className: "h-4 w-4 sm:h-5 sm:w-5 mr-1.5 sm:mr-2 shrink-0 text-gray-900" }), "Add to Cart"] })) }), _jsx(Button, { size: "lg", onClick: handleBuyNow, disabled: isOutOfStock || isBuyNowLoading || isCartLoading, className: "flex-1 min-w-0 bg-gray-900 hover:bg-gray-800 text-white rounded-full h-12 sm:h-14 px-3 sm:px-6 text-sm sm:text-base font-bold shadow-lg hover:shadow-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed", children: isBuyNowLoading || isCartLoading ? (_jsxs(_Fragment, { children: [_jsx(ButtonLoader, { className: "mr-1.5 sm:mr-2" }), "Adding..."] })) : ('Buy Now') }), storeConfig.features.wishlist && (_jsx(Button, { variant: "outline", size: "lg", onClick: handleWishlistClick, disabled: isWishlistLoading || !product, className: cn("h-12 w-12 sm:h-14 sm:w-14 shrink-0 rounded-full border-2 transition-all disabled:opacity-50 disabled:cursor-not-allowed", product && isInWishlist(product.id)
544
429
  ? "border-red-500 bg-red-50 text-red-500 hover:bg-red-100"
545
- : "border-gray-200 hover:border-gray-300 hover:bg-gray-50"), children: isWishlistLoading ? (_jsx(ButtonLoader, { className: "h-5 w-5" })) : (_jsx(Heart, { className: cn("h-5 w-5", product && isInWishlist(product.id) && "fill-current") })) }))] })] }), _jsx(ProductDeliveryInfoSection, { storeConfig: storeConfig, currency: storeConfig.settings?.currency || 'USD', productCurrency: product.currency, productPrice: product.price, quantity: quantity, shippingAddress: shippingAddress, updateShippingAddress: (patch) => setShippingAddress((prev) => ({ ...prev, ...patch })), shippingMethods: shippingMethods, isCalculatingShipping: isCalculatingShipping, shippingError: shippingError, freeShippingEligible: freeShippingEligible, freeShippingThreshold: freeShippingThreshold, onToast: addToast }), product.description && (_jsxs("div", { className: "mb-8", children: [_jsx("h2", { className: "text-lg font-semibold text-gray-900 mb-3", children: "Description" }), _jsxs("div", { className: "prose prose-sm max-w-none text-gray-600 leading-relaxed", children: [_jsx(SafeRichHtml, { value: product.description, fallbackPlain: "", as: "div" }), _jsxs("ul", { className: "list-disc pl-5 space-y-1 mt-2", children: [_jsx("li", { children: "Premium quality material" }), _jsx("li", { children: "Comfortable fit for all-day wear" }), _jsx("li", { children: "Durable stitching and finish" })] })] })] })), product.specifications && Object.keys(product.specifications).length > 0 && (_jsxs("div", { className: "mb-8", children: [_jsx("h2", { className: "text-lg font-semibold text-gray-900 mb-4", children: "Specifications" }), _jsx("dl", { className: "grid grid-cols-1 sm:grid-cols-2 gap-x-4 gap-y-4", children: Object.entries(product.specifications).map(([key, value]) => (_jsxs("div", { className: "flex flex-col p-3 bg-gray-50 rounded-lg", children: [_jsx("dt", { className: "text-xs text-gray-500 font-medium uppercase tracking-wider mb-1", children: key }), _jsx("dd", { className: "text-sm font-bold text-gray-900", children: value })] }, key))) })] }))] })] }), _jsx("div", { className: "mt-20 pt-16 border-t border-gray-200", children: _jsxs("div", { className: "max-w-4xl", children: [_jsxs("div", { className: "flex flex-col sm:flex-row items-start sm:items-center justify-between gap-6 mb-10", children: [_jsxs("div", { className: "flex-1", children: [_jsx("h2", { className: "text-2xl lg:text-3xl font-bold text-gray-900 mb-4", children: "Customer Reviews" }), product.reviewCount && product.reviewCount > 0 && product.rating && product.rating > 0 ? (_jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(StarRating, { rating: typeof product.rating === 'number' ? product.rating : Number(product.rating) || 0, size: "lg" }), _jsx("span", { className: "text-2xl font-bold text-gray-900", children: Number(product.rating).toFixed(1) }), _jsx("span", { className: "text-lg text-gray-500", children: "out of 5" })] }), _jsx("span", { className: "text-gray-300", children: "|" }), _jsxs("span", { className: "text-lg text-gray-600", children: [product.reviewCount, " ", product.reviewCount === 1 ? 'review' : 'reviews'] })] })) : (_jsx("div", { className: "text-gray-500", children: _jsx("p", { className: "text-base", children: "Be the first to review this product" }) }))] }), _jsx(Button, { onClick: () => setIsReviewModalOpen(true), className: "rounded-full shrink-0 bg-gray-900 hover:bg-gray-800 text-white", children: product.reviewCount && product.reviewCount > 0 ? 'Write a Review' : 'Be the First to Review' })] }), ratingDistribution.some(r => r.count > 0) && (_jsxs("div", { className: "mb-10 p-6 bg-gray-50 rounded-xl border border-gray-100", children: [_jsx("h3", { className: "text-sm font-semibold text-gray-900 mb-5", children: "Rating Breakdown" }), _jsx("div", { className: "space-y-3", children: ratingDistribution.map(({ rating, count, percentage }) => (_jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-1.5 w-20", children: [_jsx("span", { className: "text-sm font-semibold text-gray-900", children: rating }), _jsx(Star, { className: "h-4 w-4 fill-gray-900 text-gray-900" })] }), _jsx("div", { className: "flex-1 h-2.5 bg-gray-200 rounded-full overflow-hidden", children: _jsx("div", { className: "h-full bg-gradient-to-r from-gray-700 to-gray-900 rounded-full transition-all duration-500", style: { width: `${percentage}%` } }) }), _jsx("span", { className: "text-sm font-medium text-gray-700 w-12 text-right", children: count })] }, rating))) })] })), reviews.length > 0 ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "space-y-6", children: displayedReviews.map((review) => (_jsx(ReviewCard, { review: review }, review.id))) }), hasMoreReviews && !showAllReviews && (_jsx("div", { className: "mt-8 text-center", children: _jsxs(Button, { variant: "outline", onClick: () => setShowAllReviews(true), className: "px-8 rounded-full h-12 border-gray-300", children: ["Show All ", reviews.length, " Reviews"] }) }))] })) : (_jsxs("div", { className: "text-center py-12 bg-gray-50 rounded-xl border border-dashed border-gray-300", children: [_jsx("div", { className: "inline-flex items-center justify-center w-12 h-12 rounded-full bg-white shadow-sm mb-4", children: _jsx(Star, { className: "h-6 w-6 text-gray-400" }) }), _jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-2", children: "No Reviews Yet" }), _jsx("p", { className: "text-gray-500 max-w-sm mx-auto mb-6", children: "Be the first to review this product and help other customers make informed decisions." }), _jsx(Button, { onClick: () => setIsReviewModalOpen(true), className: "bg-gray-900 hover:bg-gray-800 text-white", children: "Write the First Review" })] }))] }) }), relatedProducts.length > 0 && (_jsxs("div", { className: "mt-20 pt-16 border-t border-gray-200", children: [_jsx("h2", { className: "text-2xl lg:text-3xl font-bold text-gray-900 mb-8", children: "You Might Also Like" }), _jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6", children: relatedProducts.map((relatedProduct) => (_jsx("div", { className: "h-[400px]", children: _jsx(ProductCard, { product: relatedProduct, storeSlug: storeConfig.slug, onAddToCart: (p) => addToCart(p, 1), onQuickView: handleQuickView, className: "h-full" }) }, relatedProduct.id))) })] }))] }) }), _jsx(Modal, { isOpen: isReviewModalOpen, onClose: () => setIsReviewModalOpen(false), title: "Write a Review", children: _jsxs("form", { onSubmit: handleSubmitReview, className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Rating" }), _jsxs("div", { className: "flex items-center gap-1", children: [[1, 2, 3, 4, 5].map((star) => (_jsxs("div", { className: "relative group", children: [_jsx("button", { type: "button", onClick: () => setNewReview({ ...newReview, rating: star - 0.5 }), className: "absolute top-0 left-0 w-1/2 h-full z-20 focus:outline-none", title: `${star - 0.5} stars` }), _jsx("button", { type: "button", onClick: () => setNewReview({ ...newReview, rating: star }), className: "absolute top-0 right-0 w-1/2 h-full z-20 focus:outline-none", title: `${star} stars` }), _jsxs("div", { className: "relative pointer-events-none transition-transform group-hover:scale-110", children: [_jsx(Star, { className: "h-8 w-8 text-gray-200 fill-gray-200" }), newReview.rating >= star - 0.5 && (_jsx("div", { className: "absolute inset-0 overflow-hidden", style: { width: newReview.rating === star - 0.5 ? '50%' : '100%' }, children: _jsx(Star, { className: "h-8 w-8 text-gray-900 fill-gray-900" }) }))] })] }, star))), _jsx("span", { className: "ml-3 text-lg font-medium text-gray-900 w-12", children: newReview.rating })] })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: "title", className: "block text-sm font-medium text-gray-700 mb-1", children: "Review Title" }), _jsx("input", { type: "text", id: "title", required: true, className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-black", placeholder: "Summarize your thoughts", value: newReview.title, onChange: (e) => setNewReview({ ...newReview, title: e.target.value }) })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: "comment", className: "block text-sm font-medium text-gray-700 mb-1", children: "Review" }), _jsx("textarea", { id: "comment", required: true, rows: 4, className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-black", placeholder: "What did you like or dislike?", value: newReview.comment, onChange: (e) => setNewReview({ ...newReview, comment: e.target.value }) })] }), _jsxs("div", { className: "pt-4 flex justify-end gap-3", children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => setIsReviewModalOpen(false), children: "Cancel" }), _jsx(Button, { type: "submit", className: "bg-black text-white hover:bg-gray-800", children: "Submit Review" })] })] }) }), _jsx(GuestCheckoutModal, { isOpen: showGuestModal, onClose: () => setShowGuestModal(false), onSubmit: handleGuestInfoSubmit, initialData: getGuestUserInfo() || undefined })] }));
430
+ : "border-gray-200 hover:border-gray-300 hover:bg-gray-50"), children: isWishlistLoading ? (_jsx(ButtonLoader, { className: "h-5 w-5" })) : (_jsx(Heart, { className: cn("h-5 w-5", product && isInWishlist(product.id) && "fill-current") })) }))] })] }), _jsx(ProductDeliveryInfoSection, { storeConfig: storeConfig, currency: storeConfig.settings?.currency || 'USD', productCurrency: product.currency, productPrice: displayPrice, quantity: quantity, shippingAddress: shippingAddress, updateShippingAddress: (patch) => setShippingAddress((prev) => ({ ...prev, ...patch })), shippingMethods: shippingMethods, isCalculatingShipping: isCalculatingShipping, shippingError: shippingError, freeShippingEligible: freeShippingEligible, freeShippingThreshold: freeShippingThreshold, onToast: addToast }), product.description && (_jsxs("div", { className: "mb-8", children: [_jsx("h2", { className: "text-lg font-semibold text-gray-900 mb-3", children: "Description" }), _jsxs("div", { className: "prose prose-sm max-w-none text-gray-600 leading-relaxed", children: [_jsx(SafeRichHtml, { value: product.description, fallbackPlain: "", as: "div" }), _jsxs("ul", { className: "list-disc pl-5 space-y-1 mt-2", children: [_jsx("li", { children: "Premium quality material" }), _jsx("li", { children: "Comfortable fit for all-day wear" }), _jsx("li", { children: "Durable stitching and finish" })] })] })] })), product.specifications && Object.keys(product.specifications).length > 0 && (_jsxs("div", { className: "mb-8", children: [_jsx("h2", { className: "text-lg font-semibold text-gray-900 mb-4", children: "Specifications" }), _jsx("dl", { className: "grid grid-cols-1 sm:grid-cols-2 gap-x-4 gap-y-4", children: Object.entries(product.specifications).map(([key, value]) => (_jsxs("div", { className: "flex flex-col p-3 bg-gray-50 rounded-lg", children: [_jsx("dt", { className: "text-xs text-gray-500 font-medium uppercase tracking-wider mb-1", children: key }), _jsx("dd", { className: "text-sm font-bold text-gray-900", children: value })] }, key))) })] }))] })] }), _jsx("div", { className: "mt-20 pt-16 border-t border-gray-200", children: _jsxs("div", { className: "max-w-4xl", children: [_jsxs("div", { className: "flex flex-col sm:flex-row items-start sm:items-center justify-between gap-6 mb-10", children: [_jsxs("div", { className: "flex-1", children: [_jsx("h2", { className: "text-2xl lg:text-3xl font-bold text-gray-900 mb-4", children: "Customer Reviews" }), reviewCount > 0 && averageRating > 0 && (_jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(StarRating, { rating: averageRating, size: "lg" }), _jsx("span", { className: "text-2xl font-bold text-gray-900", children: averageRating.toFixed(1) }), _jsx("span", { className: "text-lg text-gray-500", children: "out of 5" })] }), _jsx("span", { className: "text-gray-300", children: "|" }), _jsxs("span", { className: "text-lg text-gray-600", children: [reviewCount, " ", reviewCount === 1 ? 'review' : 'reviews'] })] }))] }), reviewCount > 0 && (_jsx(Button, { onClick: () => {
431
+ if (!isAuthenticated) {
432
+ openLogin({ onSuccess: () => setIsReviewModalOpen(true) });
433
+ return;
434
+ }
435
+ setIsReviewModalOpen(true);
436
+ }, className: "rounded-full shrink-0 bg-gray-900 hover:bg-gray-800 text-white", children: "Write a Review" }))] }), ratingDistribution.some(r => r.count > 0) && (_jsxs("div", { className: "mb-10 p-6 bg-gray-50 rounded-xl border border-gray-100", children: [_jsx("h3", { className: "text-sm font-semibold text-gray-900 mb-5", children: "Rating Breakdown" }), _jsx("div", { className: "space-y-3", children: ratingDistribution.map(({ rating, count, percentage }) => (_jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-1.5 w-20", children: [_jsx("span", { className: "text-sm font-semibold text-gray-900", children: rating }), _jsx(Star, { className: "h-4 w-4 fill-gray-900 text-gray-900" })] }), _jsx("div", { className: "flex-1 h-2.5 bg-gray-200 rounded-full overflow-hidden", children: _jsx("div", { className: "h-full bg-gradient-to-r from-gray-700 to-gray-900 rounded-full transition-all duration-500", style: { width: `${percentage}%` } }) }), _jsx("span", { className: "text-sm font-medium text-gray-700 w-12 text-right", children: count })] }, rating))) })] })), reviews.length > 0 ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "space-y-6", children: displayedReviews.map((review) => (_jsx(ReviewCard, { review: review }, review.id))) }), hasMoreReviews && !showAllReviews && (_jsx("div", { className: "mt-8 text-center", children: _jsxs(Button, { variant: "outline", onClick: () => setShowAllReviews(true), className: "px-8 rounded-full h-12 border-gray-300", children: ["Show All ", reviews.length, " Reviews"] }) }))] })) : (_jsxs("div", { className: "text-center py-12 bg-gray-50 rounded-xl border border-dashed border-gray-300", children: [_jsx("div", { className: "inline-flex items-center justify-center w-12 h-12 rounded-full bg-white shadow-sm mb-4", children: _jsx(Star, { className: "h-6 w-6 text-gray-400" }) }), _jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-2", children: "No Reviews Yet" }), _jsx("p", { className: "text-gray-500 max-w-sm mx-auto mb-6", children: "Be the first to review this product and help other customers make informed decisions." }), _jsx(Button, { onClick: () => {
437
+ if (!isAuthenticated) {
438
+ openLogin({ onSuccess: () => setIsReviewModalOpen(true) });
439
+ return;
440
+ }
441
+ setIsReviewModalOpen(true);
442
+ }, className: "bg-gray-900 hover:bg-gray-800 text-white", children: "Write a Review" })] }))] }) }), relatedProducts.length > 0 && (_jsxs("div", { className: "mt-20 pt-16 border-t border-gray-200", children: [_jsx("h2", { className: "text-2xl lg:text-3xl font-bold text-gray-900 mb-8", children: "You Might Also Like" }), _jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6", children: relatedProducts.map((relatedProduct) => (_jsx("div", { className: "h-[400px]", children: _jsx(ProductCard, { product: relatedProduct, storeSlug: storeConfig.slug, onAddToCart: (p) => addToCart(p, 1), onQuickView: handleQuickView, className: "h-full" }) }, relatedProduct.id))) })] }))] }) }), _jsx(Modal, { isOpen: isReviewModalOpen, onClose: () => setIsReviewModalOpen(false), title: "Write a Review", children: _jsxs("form", { onSubmit: handleSubmitReview, className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Rating" }), _jsxs("div", { className: "flex items-center gap-1", children: [[1, 2, 3, 4, 5].map((star) => (_jsxs("div", { className: "relative group", children: [_jsx("button", { type: "button", onClick: () => setNewReview({ ...newReview, rating: star - 0.5 }), className: "absolute top-0 left-0 w-1/2 h-full z-20 focus:outline-none", title: `${star - 0.5} stars` }), _jsx("button", { type: "button", onClick: () => setNewReview({ ...newReview, rating: star }), className: "absolute top-0 right-0 w-1/2 h-full z-20 focus:outline-none", title: `${star} stars` }), _jsxs("div", { className: "relative pointer-events-none transition-transform group-hover:scale-110", children: [_jsx(Star, { className: "h-8 w-8 text-gray-200 fill-gray-200" }), newReview.rating >= star - 0.5 && (_jsx("div", { className: "absolute inset-0 overflow-hidden", style: { width: newReview.rating === star - 0.5 ? '50%' : '100%' }, children: _jsx(Star, { className: "h-8 w-8 text-gray-900 fill-gray-900" }) }))] })] }, star))), _jsx("span", { className: "ml-3 text-lg font-medium text-gray-900 w-12", children: newReview.rating })] })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: "title", className: "block text-sm font-medium text-gray-700 mb-1", children: "Review Title" }), _jsx("input", { type: "text", id: "title", required: true, className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-black", placeholder: "Summarize your thoughts", value: newReview.title, onChange: (e) => setNewReview({ ...newReview, title: e.target.value }) })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: "comment", className: "block text-sm font-medium text-gray-700 mb-1", children: "Review" }), _jsx("textarea", { id: "comment", required: true, rows: 4, className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-black", placeholder: "What did you like or dislike?", value: newReview.comment, onChange: (e) => setNewReview({ ...newReview, comment: e.target.value }) })] }), _jsxs("div", { className: "pt-4 flex justify-end gap-3", children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => setIsReviewModalOpen(false), children: "Cancel" }), _jsx(Button, { type: "submit", className: "bg-black text-white hover:bg-gray-800", children: "Submit Review" })] })] }) }), _jsx(GuestCheckoutModal, { isOpen: showGuestModal, onClose: () => setShowGuestModal(false), onSubmit: handleGuestInfoSubmit, initialData: getGuestUserInfo() || undefined })] }));
546
443
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ProductsPage.d.ts","sourceRoot":"","sources":["../../../../src/layouts/food/pages/ProductsPage.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAgB,MAAM,mBAAmB,CAAC;AAkC9D,UAAU,iBAAiB;IACzB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAqlBD,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,+BAMpD"}
1
+ {"version":3,"file":"ProductsPage.d.ts","sourceRoot":"","sources":["../../../../src/layouts/food/pages/ProductsPage.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAgB,MAAM,mBAAmB,CAAC;AAmC9D,UAAU,iBAAiB;IACzB,WAAW,EAAE,WAAW,CAAC;CAC1B;AA6kBD,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,+BAMpD"}
@@ -17,6 +17,7 @@ import { useFilteredProductsFetch } from '../../../hooks/use-filtered-products-f
17
17
  import { formatCurrency } from '../../../lib/utils/index.js';
18
18
  import { getBannerImage, getLayoutText } from '../../../lib/utils/asset-helpers.js';
19
19
  import { shouldUseAPI } from '../../../lib/utils/demo-detection.js';
20
+ import { productCategoryFilterChipClass } from '../../shared/product-category-filter-chip';
20
21
  import { useCatalogPriceRange } from '../../../hooks/use-catalog-price-range.js';
21
22
  import { getApiPriceFilterActive, isWithinActivePriceRange } from '../../../lib/utils/catalog-price-bounds.js';
22
23
  import { buildFilteredProductQuery, getFilteredProductQueryKey, productMatchesCategoryIds, productMatchesCategorySlug, } from '../../../lib/utils/filtered-product-query.js';
@@ -239,11 +240,7 @@ function ProductsPageContent({ storeConfig: initialConfig }) {
239
240
  element?.scrollIntoView({ behavior: 'smooth' });
240
241
  }, children: primaryButtonText })), secondaryButtonText && secondaryButtonText.trim() !== '' && (_jsx(Button, { className: `h-12 px-8 rounded-full text-base font-medium shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1 ${categoryHeaderImage ? 'bg-white/10 backdrop-blur-md text-white border border-white/30 hover:bg-white/20' : 'bg-gray-100 text-gray-900 hover:bg-gray-200 border border-gray-200'}`, onClick: () => {
241
242
  window.location.href = `/${storeConfig.slug}/categories`;
242
- }, children: secondaryButtonText }))] })] }) }) })] })), productsConfig?.productsGrid?.show !== false && (_jsxs(_Fragment, { children: [_jsxs("div", { "data-content-ready": true, className: "container mx-auto px-4 sm:px-6 lg:px-8 py-8", children: [_jsxs("div", { className: "flex gap-3 mb-8 overflow-x-auto pb-2 scrollbar-hide", children: [_jsx("button", { onClick: () => setSelectedCategoryFilter(null), className: `px-6 py-2.5 rounded-lg text-sm font-semibold whitespace-nowrap transition-all ${!selectedCategoryFilter
243
- ? 'bg-gray-900 text-white hover:bg-blue-600'
244
- : 'bg-gray-100 text-gray-700 hover:bg-blue-50 hover:text-blue-600'}`, children: "All" }), categories.map((category) => (_jsx("button", { onClick: () => setSelectedCategoryFilter(selectedCategoryFilter === category.slug ? null : category.slug), className: `px-6 py-2.5 rounded-lg text-sm font-semibold whitespace-nowrap transition-all ${selectedCategoryFilter === category.slug
245
- ? 'bg-gray-900 text-white hover:bg-blue-600'
246
- : 'bg-gray-100 text-gray-700 hover:bg-blue-50 hover:text-blue-600'}`, children: category.name }, category.id)))] }), _jsxs("div", { id: "product-grid", className: "grid grid-cols-1 lg:grid-cols-4 gap-8", children: [_jsxs("aside", { className: "hidden lg:block col-span-1 space-y-8 sticky top-24 h-fit max-h-[calc(100vh-8rem)] overflow-y-auto pr-2 scrollbar-thin scrollbar-thumb-gray-200 scrollbar-track-transparent", children: [_jsxs("div", { className: "flex items-center justify-between pb-4 border-b border-gray-100", children: [_jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Filters" }), hasActiveFilters && (_jsxs("button", { onClick: clearAllFilters, className: "text-xs font-medium text-red-500 hover:text-red-700 flex items-center gap-1", children: [_jsx(RotateCcw, { className: "h-3 w-3" }), " Reset"] }))] }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-gray-900 uppercase tracking-wide mb-4", children: "Search" }), _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" }), _jsx("input", { type: "text", placeholder: "Search items...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), className: "w-full pl-10 pr-4 py-2 bg-gray-50 border border-gray-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-500/20 focus:border-orange-500 transition-all" })] })] }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-gray-900 uppercase tracking-wide mb-4", children: "Category" }), _jsx("div", { className: "space-y-2", children: categories.map((category) => (_jsxs("label", { className: "flex items-center gap-3 cursor-pointer group p-2 -ml-2 rounded-lg hover:bg-gray-50 transition-colors", children: [_jsx("div", { className: `w-5 h-5 rounded border flex items-center justify-center transition-colors ${selectedCategories.includes(category.id) ? 'bg-orange-500 border-orange-500' : 'border-gray-300 group-hover:border-orange-400'}`, children: selectedCategories.includes(category.id) && _jsx(Check, { className: "h-3 w-3 text-white" }) }), _jsx("input", { type: "checkbox", className: "hidden", checked: selectedCategories.includes(category.id), onChange: () => setSelectedCategories(prev => prev.includes(category.id) ? prev.filter(id => id !== category.id) : [...prev, category.id]) }), _jsx("span", { className: "text-sm text-gray-700 font-medium", children: category.name })] }, category.id))) })] }), _jsx(ProductPriceFilterSection, { currency: storeCurrency, priceRange: priceRange, onPriceRangeChange: setPriceRange, bounds: catalogBounds, isFilterActive: hasActivePriceFilter, onReset: resetPriceRange })] }), _jsxs("div", { className: "lg:col-span-3", children: [_jsxs("div", { className: "lg:hidden mb-6 flex items-center justify-between", children: [_jsxs(Button, { onClick: () => setIsFilterOpen(true), variant: "outline", className: "flex items-center gap-2", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), " Filters"] }), _jsxs("select", { value: sortBy, onChange: (e) => setSortBy(e.target.value), className: "bg-transparent text-sm font-medium text-gray-700 focus:outline-none", children: [_jsx("option", { value: "featured", children: "Sort by: Featured" }), _jsx("option", { value: "price-asc", children: "Sort by: Price Low-High" }), _jsx("option", { value: "price-desc", children: "Sort by: Price High-Low" })] })] }), _jsxs("div", { className: "hidden lg:flex items-center justify-between mb-6", children: [_jsxs("p", { className: "text-sm text-gray-600", children: ["Showing ", _jsx("span", { className: "font-semibold text-gray-900", children: filteredAndSortedItems.length }), " items"] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm text-gray-500", children: "Sort by:" }), _jsxs("select", { value: sortBy, onChange: (e) => setSortBy(e.target.value), className: "bg-transparent text-sm font-medium text-gray-900 focus:outline-none cursor-pointer hover:text-orange-600 transition-colors", children: [_jsx("option", { value: "featured", children: "Featured" }), _jsx("option", { value: "price-asc", children: "Price: Low to High" }), _jsx("option", { value: "price-desc", children: "Price: High to Low" })] })] })] }), isLoadingProducts ? (_jsx(ProductGridSkeleton, { count: 12, columns: 3 })) : displayedItems.length > 0 ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: displayedItems.map((item) => (_jsxs("div", { className: "group bg-white rounded-2xl overflow-hidden shadow-sm hover:shadow-xl transition-all duration-300 border border-gray-100 flex flex-col h-full", children: [_jsxs("div", { className: "relative aspect-square overflow-hidden", children: [_jsx(ImageWithFallback, { src: item.image, alt: item.name, className: "w-full h-full object-cover transition-transform duration-700 group-hover:scale-110", skeletonAspectRatio: "square" }), _jsx("div", { className: "absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center", children: _jsx(Button, { className: "rounded-full bg-white text-black hover:bg-orange-600 hover:text-white transform translate-y-4 group-hover:translate-y-0 transition-all duration-300 shadow-lg font-bold px-8", onClick: () => handleAddToCart(item), children: "Add to Order" }) })] }), _jsxs("div", { className: "p-5 flex flex-col flex-1", children: [_jsxs("div", { className: "flex justify-between items-start mb-2", children: [_jsx("h3", { className: "font-bold text-lg text-gray-900 leading-tight group-hover:text-orange-600 transition-colors", children: item.name }), _jsx("span", { className: "font-black text-lg text-gray-900 ml-2", children: formatCurrency(item.price, storeConfig.settings?.currency || 'USD') })] }), _jsx(RichTextExcerpt, { value: item.description, as: "p", className: "text-gray-500 text-sm line-clamp-2 leading-relaxed flex-1" })] })] }, item.id))) }), hasMore && (_jsx("div", { className: "mt-12 text-center", children: _jsx(Button, { onClick: handleLoadMore, variant: "outline", className: "min-w-[200px] rounded-full font-bold h-12 border-gray-200 hover:border-orange-500 hover:text-orange-600", children: "Load More" }) }))] })) : (_jsxs("div", { className: "flex flex-col items-center justify-center py-20 bg-white rounded-3xl border border-dashed border-gray-200 text-center", children: [_jsx("div", { className: "w-16 h-16 bg-gray-50 rounded-full flex items-center justify-center mb-6 text-gray-300", children: _jsx(Search, { className: "h-8 w-8" }) }), _jsx("h3", { className: "text-xl font-bold text-gray-900 mb-2", children: "No items found" }), _jsx("p", { className: "text-gray-500 mb-8 max-w-sm mx-auto", children: "We couldn't find anything matching your filters. Try adjusting your search or clearing filters." }), _jsx(Button, { onClick: clearAllFilters, className: "rounded-full px-8 bg-gray-900 hover:bg-orange-600", children: "Clear All Filters" })] }))] })] })] }), _jsxs(Sheet, { isOpen: isFilterOpen, onClose: () => setIsFilterOpen(false), title: "Filters", side: "right", children: [_jsxs("div", { className: "flex flex-col h-full overflow-y-auto pb-24 space-y-8 p-1", children: [_jsxs("div", { children: [_jsx("h3", { className: "font-bold text-sm uppercase tracking-widest mb-4", children: "Search" }), _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" }), _jsx("input", { type: "text", placeholder: "Search items...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), className: "w-full pl-10 pr-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-orange-500/20" })] })] }), _jsxs("div", { children: [_jsx("h3", { className: "font-bold text-sm uppercase tracking-widest mb-4", children: "Categories" }), _jsx("div", { className: "space-y-3", children: categories.map((category) => (_jsxs("label", { className: "flex items-center gap-3", children: [_jsx("input", { type: "checkbox", checked: selectedCategories.includes(category.id), onChange: () => setSelectedCategories(prev => prev.includes(category.id) ? prev.filter(id => id !== category.id) : [...prev, category.id]), className: "w-5 h-5 rounded border-gray-300 text-orange-600 focus:ring-orange-500" }), _jsx("span", { className: "text-sm text-gray-700 font-medium", children: category.name })] }, category.id))) })] }), _jsx(ProductPriceFilterSection, { currency: storeCurrency, priceRange: priceRange, onPriceRangeChange: setPriceRange, bounds: catalogBounds, isFilterActive: hasActivePriceFilter, onReset: resetPriceRange })] }), _jsxs("div", { className: "absolute bottom-0 left-0 right-0 p-4 bg-white border-t border-gray-100 flex gap-3", children: [_jsx(Button, { onClick: clearAllFilters, variant: "outline", className: "flex-1 h-12 font-bold rounded-xl border-gray-300", children: "Reset" }), _jsx(Button, { onClick: () => setIsFilterOpen(false), className: "flex-[2] bg-orange-600 text-white hover:bg-orange-700 h-12 font-bold rounded-xl transition-colors", children: "View Results" })] })] })] }))] }));
243
+ }, children: secondaryButtonText }))] })] }) }) })] })), productsConfig?.productsGrid?.show !== false && (_jsxs(_Fragment, { children: [_jsxs("div", { "data-content-ready": true, className: "container mx-auto px-4 sm:px-6 lg:px-8 py-8", children: [_jsxs("div", { className: "flex gap-3 mb-8 overflow-x-auto pb-2 scrollbar-hide", children: [_jsx("button", { onClick: () => setSelectedCategoryFilter(null), className: productCategoryFilterChipClass(!selectedCategoryFilter), children: "All" }), categories.map((category) => (_jsx("button", { onClick: () => setSelectedCategoryFilter(selectedCategoryFilter === category.slug ? null : category.slug), className: productCategoryFilterChipClass(selectedCategoryFilter === category.slug), children: category.name }, category.id)))] }), _jsxs("div", { id: "product-grid", className: "grid grid-cols-1 lg:grid-cols-4 gap-8", children: [_jsxs("aside", { className: "hidden lg:block col-span-1 space-y-8 sticky top-24 h-fit max-h-[calc(100vh-8rem)] overflow-y-auto pr-2 scrollbar-thin scrollbar-thumb-gray-200 scrollbar-track-transparent", children: [_jsxs("div", { className: "flex items-center justify-between pb-4 border-b border-gray-100", children: [_jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Filters" }), hasActiveFilters && (_jsxs("button", { onClick: clearAllFilters, className: "text-xs font-medium text-red-500 hover:text-red-700 flex items-center gap-1", children: [_jsx(RotateCcw, { className: "h-3 w-3" }), " Reset"] }))] }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-gray-900 uppercase tracking-wide mb-4", children: "Search" }), _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" }), _jsx("input", { type: "text", placeholder: "Search items...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), className: "w-full pl-10 pr-4 py-2 bg-gray-50 border border-gray-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-500/20 focus:border-orange-500 transition-all" })] })] }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-gray-900 uppercase tracking-wide mb-4", children: "Category" }), _jsx("div", { className: "space-y-2", children: categories.map((category) => (_jsxs("label", { className: "flex items-center gap-3 cursor-pointer group p-2 -ml-2 rounded-lg hover:bg-gray-50 transition-colors", children: [_jsx("div", { className: `w-5 h-5 rounded border flex items-center justify-center transition-colors ${selectedCategories.includes(category.id) ? 'bg-orange-500 border-orange-500' : 'border-gray-300 group-hover:border-orange-400'}`, children: selectedCategories.includes(category.id) && _jsx(Check, { className: "h-3 w-3 text-white" }) }), _jsx("input", { type: "checkbox", className: "hidden", checked: selectedCategories.includes(category.id), onChange: () => setSelectedCategories(prev => prev.includes(category.id) ? prev.filter(id => id !== category.id) : [...prev, category.id]) }), _jsx("span", { className: "text-sm text-gray-700 font-medium", children: category.name })] }, category.id))) })] }), _jsx(ProductPriceFilterSection, { currency: storeCurrency, priceRange: priceRange, onPriceRangeChange: setPriceRange, bounds: catalogBounds, isFilterActive: hasActivePriceFilter, onReset: resetPriceRange })] }), _jsxs("div", { className: "lg:col-span-3", children: [_jsxs("div", { className: "lg:hidden mb-6 flex items-center justify-between", children: [_jsxs(Button, { onClick: () => setIsFilterOpen(true), variant: "outline", className: "flex items-center gap-2", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), " Filters"] }), _jsxs("select", { value: sortBy, onChange: (e) => setSortBy(e.target.value), className: "bg-transparent text-sm font-medium text-gray-700 focus:outline-none", children: [_jsx("option", { value: "featured", children: "Sort by: Featured" }), _jsx("option", { value: "price-asc", children: "Sort by: Price Low-High" }), _jsx("option", { value: "price-desc", children: "Sort by: Price High-Low" })] })] }), _jsxs("div", { className: "hidden lg:flex items-center justify-between mb-6", children: [_jsxs("p", { className: "text-sm text-gray-600", children: ["Showing ", _jsx("span", { className: "font-semibold text-gray-900", children: filteredAndSortedItems.length }), " items"] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm text-gray-500", children: "Sort by:" }), _jsxs("select", { value: sortBy, onChange: (e) => setSortBy(e.target.value), className: "bg-transparent text-sm font-medium text-gray-900 focus:outline-none cursor-pointer hover:text-orange-600 transition-colors", children: [_jsx("option", { value: "featured", children: "Featured" }), _jsx("option", { value: "price-asc", children: "Price: Low to High" }), _jsx("option", { value: "price-desc", children: "Price: High to Low" })] })] })] }), isLoadingProducts ? (_jsx(ProductGridSkeleton, { count: 12, columns: 3 })) : displayedItems.length > 0 ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: displayedItems.map((item) => (_jsxs("div", { className: "group bg-white rounded-2xl overflow-hidden shadow-sm hover:shadow-xl transition-all duration-300 border border-gray-100 flex flex-col h-full", children: [_jsxs("div", { className: "relative aspect-square overflow-hidden", children: [_jsx(ImageWithFallback, { src: item.image, alt: item.name, className: "w-full h-full object-cover transition-transform duration-700 group-hover:scale-110", skeletonAspectRatio: "square" }), _jsx("div", { className: "absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center", children: _jsx(Button, { className: "rounded-full bg-white text-black hover:bg-orange-600 hover:text-white transform translate-y-4 group-hover:translate-y-0 transition-all duration-300 shadow-lg font-bold px-8", onClick: () => handleAddToCart(item), children: "Add to Order" }) })] }), _jsxs("div", { className: "p-5 flex flex-col flex-1", children: [_jsxs("div", { className: "flex justify-between items-start mb-2", children: [_jsx("h3", { className: "font-bold text-lg text-gray-900 leading-tight group-hover:text-orange-600 transition-colors", children: item.name }), _jsx("span", { className: "font-black text-lg text-gray-900 ml-2", children: formatCurrency(item.price, storeConfig.settings?.currency || 'USD') })] }), _jsx(RichTextExcerpt, { value: item.description, as: "p", className: "text-gray-500 text-sm line-clamp-2 leading-relaxed flex-1" })] })] }, item.id))) }), hasMore && (_jsx("div", { className: "mt-12 text-center", children: _jsx(Button, { onClick: handleLoadMore, variant: "outline", className: "min-w-[200px] rounded-full font-bold h-12 border-gray-200 hover:border-orange-500 hover:text-orange-600", children: "Load More" }) }))] })) : (_jsxs("div", { className: "flex flex-col items-center justify-center py-20 bg-white rounded-3xl border border-dashed border-gray-200 text-center", children: [_jsx("div", { className: "w-16 h-16 bg-gray-50 rounded-full flex items-center justify-center mb-6 text-gray-300", children: _jsx(Search, { className: "h-8 w-8" }) }), _jsx("h3", { className: "text-xl font-bold text-gray-900 mb-2", children: "No items found" }), _jsx("p", { className: "text-gray-500 mb-8 max-w-sm mx-auto", children: "We couldn't find anything matching your filters. Try adjusting your search or clearing filters." }), _jsx(Button, { onClick: clearAllFilters, className: "rounded-full px-8 bg-gray-900 hover:bg-orange-600", children: "Clear All Filters" })] }))] })] })] }), _jsxs(Sheet, { isOpen: isFilterOpen, onClose: () => setIsFilterOpen(false), title: "Filters", side: "right", children: [_jsxs("div", { className: "flex flex-col h-full overflow-y-auto pb-24 space-y-8 p-1", children: [_jsxs("div", { children: [_jsx("h3", { className: "font-bold text-sm uppercase tracking-widest mb-4", children: "Search" }), _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" }), _jsx("input", { type: "text", placeholder: "Search items...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), className: "w-full pl-10 pr-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-orange-500/20" })] })] }), _jsxs("div", { children: [_jsx("h3", { className: "font-bold text-sm uppercase tracking-widest mb-4", children: "Categories" }), _jsx("div", { className: "space-y-3", children: categories.map((category) => (_jsxs("label", { className: "flex items-center gap-3", children: [_jsx("input", { type: "checkbox", checked: selectedCategories.includes(category.id), onChange: () => setSelectedCategories(prev => prev.includes(category.id) ? prev.filter(id => id !== category.id) : [...prev, category.id]), className: "w-5 h-5 rounded border-gray-300 text-orange-600 focus:ring-orange-500" }), _jsx("span", { className: "text-sm text-gray-700 font-medium", children: category.name })] }, category.id))) })] }), _jsx(ProductPriceFilterSection, { currency: storeCurrency, priceRange: priceRange, onPriceRangeChange: setPriceRange, bounds: catalogBounds, isFilterActive: hasActivePriceFilter, onReset: resetPriceRange })] }), _jsxs("div", { className: "absolute bottom-0 left-0 right-0 p-4 bg-white border-t border-gray-100 flex gap-3", children: [_jsx(Button, { onClick: clearAllFilters, variant: "outline", className: "flex-1 h-12 font-bold rounded-xl border-gray-300", children: "Reset" }), _jsx(Button, { onClick: () => setIsFilterOpen(false), className: "flex-[2] bg-orange-600 text-white hover:bg-orange-700 h-12 font-bold rounded-xl transition-colors", children: "View Results" })] })] })] }))] }));
247
244
  }
248
245
  export function ProductsPage(props) {
249
246
  return (_jsx(Suspense, { fallback: _jsx("div", { className: "min-h-screen flex items-center justify-center", children: _jsx("div", { className: "w-8 h-8 border-2 border-black border-t-transparent rounded-full animate-spin" }) }), children: _jsx(ProductsPageContent, { ...props }) }));
@@ -132,7 +132,7 @@ export function MenuPage({ storeConfig: initialConfig, categorySlug }) {
132
132
  ? "text-white border-transparent shadow-md transform scale-105"
133
133
  : "bg-white text-gray-600 border-gray-200 hover:bg-gray-100 hover:border-gray-300"), style: !selectedCategory ? { backgroundColor: primaryColor } : {}, children: "All Items" }), categories.map((category) => (_jsx("button", { onClick: () => setSelectedCategory(category.slug), className: cn("px-4 py-2 rounded-full text-sm font-bold whitespace-nowrap transition-all border flex-shrink-0 select-none", selectedCategory === category.slug
134
134
  ? "text-white border-transparent shadow-md transform scale-105"
135
- : "bg-white text-gray-600 border-gray-200 hover:bg-gray-100 hover:border-gray-300"), style: selectedCategory === category.slug ? { backgroundColor: primaryColor } : {}, children: category.name }, category.id)))] })] })] }) }) }), _jsxs("div", { className: "container mx-auto px-4 sm:px-6 lg:px-8 py-12", children: [selectedCategory && (_jsx("div", { className: "mb-8 animate-fade-in", children: _jsxs("h2", { className: "text-2xl font-bold text-gray-900 capitalize flex items-center gap-3", children: [categories.find(c => c.slug === selectedCategory)?.name || 'Category', _jsxs("span", { className: "text-sm font-normal text-gray-500 bg-gray-100 px-2 py-0.5 rounded-full", children: [filteredItems.length, " items"] })] }) })), _jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8", children: filteredItems.map((item, index) => (_jsxs(Card, { className: "group overflow-hidden hover:shadow-xl transition-all duration-300 border-gray-100 flex flex-col h-full bg-white rounded-2xl", children: [_jsxs("div", { className: "relative aspect-[4/3] overflow-hidden", children: [_jsx(ImageWithFallback, { src: item.image, alt: item.name, className: "w-full h-full object-cover transition-transform duration-700 group-hover:scale-110", skeletonAspectRatio: "4/3" }), _jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-end justify-center pb-6", children: _jsx(Button, { className: "rounded-full font-bold shadow-lg transform translate-y-4 group-hover:translate-y-0 transition-transform duration-300", style: { backgroundColor: primaryColor, color: 'white' }, children: "Quick Add" }) }), index % 3 === 0 && (_jsxs("div", { className: "absolute top-3 right-3 bg-white/90 backdrop-blur text-gray-900 text-[10px] font-bold px-2 py-1 rounded-full shadow-sm flex items-center gap-1", children: [_jsx(Star, { className: "w-3 h-3 fill-yellow-400 text-yellow-400" }), " Popular"] }))] }), _jsxs(CardContent, { className: "p-6 flex-1 flex flex-col", children: [_jsxs("div", { className: "flex justify-between items-start mb-2", children: [_jsx("h3", { className: "font-bold text-lg text-gray-900 leading-tight", children: item.name }), _jsx("span", { className: "font-black text-lg text-gray-900 whitespace-nowrap ml-2", children: formatCurrency(item.price, item.currency || storeConfig.settings?.currency || 'USD') })] }), _jsx(RichTextExcerpt, { value: item.description, as: "p", className: "text-gray-500 text-sm mb-4 line-clamp-2 leading-relaxed flex-1" }), item.dietaryInfo && item.dietaryInfo.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2 mb-6", children: item.dietaryInfo.map((info) => {
135
+ : "bg-white text-gray-600 border-gray-200 hover:bg-gray-100 hover:border-gray-300"), style: selectedCategory === category.slug ? { backgroundColor: primaryColor } : {}, children: category.name }, category.id)))] })] })] }) }) }), _jsxs("div", { className: "container mx-auto px-4 sm:px-6 lg:px-8 py-12", children: [selectedCategory && (_jsx("div", { className: "mb-8 animate-fade-in", children: _jsxs("h2", { className: "text-2xl font-bold text-gray-900 capitalize flex items-center gap-3", children: [categories.find(c => c.slug === selectedCategory)?.name || 'Category', _jsxs("span", { className: "text-sm font-normal text-gray-500 bg-gray-100 px-2 py-0.5 rounded-full", children: [filteredItems.length, " items"] })] }) })), _jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8", children: filteredItems.map((item, index) => (_jsxs(Card, { className: "group overflow-hidden hover:shadow-xl transition-all duration-300 border-gray-100 flex flex-col h-full bg-white rounded-2xl", children: [_jsxs("div", { className: "relative aspect-[4/3] overflow-hidden", children: [_jsx(ImageWithFallback, { src: item.image, alt: item.name, className: "w-full h-full object-cover transition-transform duration-700 group-hover:scale-110", skeletonAspectRatio: "4/3" }), _jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-end justify-center pb-6", children: _jsx(Button, { className: "rounded-full font-bold shadow-lg transform translate-y-4 group-hover:translate-y-0 transition-transform duration-300", style: { backgroundColor: primaryColor, color: 'white' }, children: "Add to cart" }) }), index % 3 === 0 && (_jsxs("div", { className: "absolute top-3 right-3 bg-white/90 backdrop-blur text-gray-900 text-[10px] font-bold px-2 py-1 rounded-full shadow-sm flex items-center gap-1", children: [_jsx(Star, { className: "w-3 h-3 fill-yellow-400 text-yellow-400" }), " Popular"] }))] }), _jsxs(CardContent, { className: "p-6 flex-1 flex flex-col", children: [_jsxs("div", { className: "flex justify-between items-start mb-2", children: [_jsx("h3", { className: "font-bold text-lg text-gray-900 leading-tight", children: item.name }), _jsx("span", { className: "font-black text-lg text-gray-900 whitespace-nowrap ml-2", children: formatCurrency(item.price, item.currency || storeConfig.settings?.currency || 'USD') })] }), _jsx(RichTextExcerpt, { value: item.description, as: "p", className: "text-gray-500 text-sm mb-4 line-clamp-2 leading-relaxed flex-1" }), item.dietaryInfo && item.dietaryInfo.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2 mb-6", children: item.dietaryInfo.map((info) => {
136
136
  const Icon = dietaryIcons[info.toLowerCase()] || Info;
137
137
  return (_jsxs("span", { className: "inline-flex items-center gap-1 text-[10px] uppercase font-bold px-2 py-1 rounded bg-gray-50 text-gray-500 border border-gray-100", children: [_jsx(Icon, { className: "w-3 h-3" }), info] }, info));
138
138
  }) })), _jsxs(Button, { className: "w-full rounded-xl font-bold h-11 shadow-sm hover:shadow-md transition-all active:scale-[0.98]", style: {
@@ -1 +1 @@
1
- {"version":3,"file":"ProductDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/layouts/food-modern/pages/ProductDetailPage.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,EAA8C,MAAM,mBAAmB,CAAC;AA6B5F,UAAU,sBAAsB;IAC9B,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB;AAsHD,wBAAgB,iBAAiB,CAAC,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,EAAE,sBAAsB,+BAwiCpG"}
1
+ {"version":3,"file":"ProductDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/layouts/food-modern/pages/ProductDetailPage.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,EAA8C,MAAM,mBAAmB,CAAC;AAiC5F,UAAU,sBAAsB;IAC9B,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB;AAsHD,wBAAgB,iBAAiB,CAAC,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,EAAE,sBAAsB,+BA44BpG"}