@sonordev/site-kit 1.4.5 → 1.4.6

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.
@@ -313,6 +313,26 @@ async function registerForEvent(eventId, scheduleId, customer) {
313
313
  return { success: false, error: "Network error. Please try again." };
314
314
  }
315
315
  }
316
+ async function validateDiscountCode(code, subtotal, offeringIds = []) {
317
+ const { apiUrl, apiKey } = getApiConfig();
318
+ try {
319
+ const response = await fetch(`${apiUrl}/api/public/commerce/discount/validate`, {
320
+ method: "POST",
321
+ headers: {
322
+ "Content-Type": "application/json",
323
+ "x-api-key": apiKey
324
+ },
325
+ body: JSON.stringify({ code, subtotal, offeringIds })
326
+ });
327
+ if (!response.ok) {
328
+ const body = await response.json().catch(() => ({}));
329
+ return { valid: false, message: body?.message || "Invalid discount code" };
330
+ }
331
+ return await response.json();
332
+ } catch {
333
+ return { valid: false, message: "Could not validate discount code" };
334
+ }
335
+ }
316
336
  async function fetchProcessorConfig() {
317
337
  const { apiUrl, apiKey } = getApiConfig();
318
338
  if (!apiKey) return null;
@@ -1701,7 +1721,7 @@ var ICON_MAP = {
1701
1721
  return: IconReturn,
1702
1722
  shield: IconShield
1703
1723
  };
1704
- function ProductGallery({ images, productName, selectedIndex, onSelect }) {
1724
+ function ProductGallery({ images, productName, selectedIndex, onSelect, imageIds, imageModelInfo }) {
1705
1725
  const [zoomStyle, setZoomStyle] = useState(null);
1706
1726
  const [isHovering, setIsHovering] = useState(false);
1707
1727
  const handleMouseMove = (e) => {
@@ -1742,6 +1762,39 @@ function ProductGallery({ images, productName, selectedIndex, onSelect }) {
1742
1762
  draggable: false
1743
1763
  }
1744
1764
  ),
1765
+ (() => {
1766
+ const currentImgId = imageIds?.[selectedIndex];
1767
+ const modelInfo = currentImgId && imageModelInfo?.[currentImgId];
1768
+ if (!modelInfo) return null;
1769
+ const parts = [];
1770
+ if (modelInfo.height) parts.push(modelInfo.height);
1771
+ if (modelInfo.weight) parts.push(modelInfo.weight);
1772
+ const line1 = parts.length ? `Model: ${parts.join(", ")}` : modelInfo.model_name ? `Model: ${modelInfo.model_name}` : null;
1773
+ const line2 = modelInfo.wearing_size ? `Wearing size ${modelInfo.wearing_size}` : null;
1774
+ if (!line1 && !line2) return null;
1775
+ return /* @__PURE__ */ jsxs(
1776
+ "div",
1777
+ {
1778
+ style: {
1779
+ position: "absolute",
1780
+ bottom: 0,
1781
+ left: 0,
1782
+ right: 0,
1783
+ background: "linear-gradient(transparent, rgba(0,0,0,0.65))",
1784
+ padding: "1.5rem 1rem 0.75rem",
1785
+ color: "#fff",
1786
+ fontSize: "0.8125rem",
1787
+ lineHeight: 1.4,
1788
+ pointerEvents: "none",
1789
+ zIndex: 2
1790
+ },
1791
+ children: [
1792
+ line1 && /* @__PURE__ */ jsx("div", { style: { fontWeight: 600 }, children: line1 }),
1793
+ line2 && /* @__PURE__ */ jsx("div", { style: { fontWeight: 400, opacity: 0.9 }, children: line2 })
1794
+ ]
1795
+ }
1796
+ );
1797
+ })(),
1745
1798
  images.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
1746
1799
  /* @__PURE__ */ jsx(
1747
1800
  "button",
@@ -1868,20 +1921,33 @@ function VariantSelector({ variants, selected, isClothing, showInventory, onSele
1868
1921
  disabled: variantOutOfStock,
1869
1922
  style: {
1870
1923
  position: "relative",
1871
- padding: "0.5rem 1rem",
1924
+ padding: variantOutOfStock ? "0.375rem 1rem 0.25rem" : "0.5rem 1rem",
1872
1925
  borderRadius: "var(--commerce-radius-sm, 8px)",
1873
- border: isSelected ? "2px solid var(--commerce-accent, #2563eb)" : "1px solid var(--commerce-border, #e5e7eb)",
1874
- backgroundColor: isSelected ? "color-mix(in srgb, var(--commerce-accent, #2563eb) 8%, transparent)" : "var(--commerce-surface, #f9fafb)",
1926
+ border: isSelected ? "2px solid var(--commerce-accent, #2563eb)" : variantOutOfStock ? "1px solid var(--commerce-border, #e5e7eb)" : "1px solid var(--commerce-border, #e5e7eb)",
1927
+ backgroundColor: isSelected ? "color-mix(in srgb, var(--commerce-accent, #2563eb) 8%, transparent)" : variantOutOfStock ? "var(--commerce-surface, #f9fafb)" : "var(--commerce-surface, #f9fafb)",
1875
1928
  color: variantOutOfStock ? "var(--commerce-text-muted, #9ca3af)" : "var(--commerce-text, #111827)",
1876
1929
  fontSize: "0.875rem",
1877
1930
  fontWeight: 500,
1878
1931
  cursor: variantOutOfStock ? "not-allowed" : "pointer",
1879
- opacity: variantOutOfStock ? 0.5 : 1,
1932
+ opacity: variantOutOfStock ? 0.6 : 1,
1880
1933
  transition: "all 0.15s ease",
1881
- textDecoration: variantOutOfStock ? "line-through" : "none"
1934
+ textDecoration: "none",
1935
+ display: "flex",
1936
+ flexDirection: "column",
1937
+ alignItems: "center",
1938
+ gap: variantOutOfStock ? "1px" : 0,
1939
+ minWidth: 48
1882
1940
  },
1883
1941
  children: [
1884
- variant.options?.Size || variant.name,
1942
+ /* @__PURE__ */ jsx("span", { style: { textDecoration: variantOutOfStock ? "line-through" : "none" }, children: variant.options?.Size || variant.name }),
1943
+ variantOutOfStock && /* @__PURE__ */ jsx("span", { style: {
1944
+ fontSize: "0.5625rem",
1945
+ fontWeight: 600,
1946
+ color: "var(--commerce-danger, #dc2626)",
1947
+ textTransform: "uppercase",
1948
+ letterSpacing: "0.03em",
1949
+ lineHeight: 1
1950
+ }, children: "Sold Out" }),
1885
1951
  showInventory && !variantOutOfStock && /* @__PURE__ */ jsx(
1886
1952
  "span",
1887
1953
  {
@@ -3118,6 +3184,21 @@ function ProductDetail({
3118
3184
  ...product.variants?.map((v) => v.image_url).filter(Boolean) || []
3119
3185
  ].filter(Boolean);
3120
3186
  }, [product]);
3187
+ const allImageIds = useMemo(() => {
3188
+ if (!product) return [];
3189
+ const ids = [
3190
+ product.featured_image_id,
3191
+ ...product.gallery_image_ids || [],
3192
+ ...product.variants?.map((v) => v.id) || []
3193
+ // variant images don't have separate IDs for model info
3194
+ ];
3195
+ const urls = [
3196
+ product.featured_image_url,
3197
+ ...product.gallery_images || [],
3198
+ ...product.variants?.map((v) => v.image_url).filter(Boolean) || []
3199
+ ];
3200
+ return urls.map((url, i) => url ? ids[i] : void 0).filter((_id, i) => urls[i]);
3201
+ }, [product]);
3121
3202
  const currentPrice = product ? selectedVariant?.price ?? product.price ?? 0 : 0;
3122
3203
  const compareAtPrice = product?.compare_at_price;
3123
3204
  const hasDiscount = compareAtPrice != null && compareAtPrice > currentPrice;
@@ -3326,7 +3407,9 @@ function ProductDetail({
3326
3407
  images: allImages,
3327
3408
  productName: product.name,
3328
3409
  selectedIndex: selectedImage,
3329
- onSelect: setSelectedImage
3410
+ onSelect: setSelectedImage,
3411
+ imageIds: allImageIds,
3412
+ imageModelInfo: product.metadata?.image_model_info
3330
3413
  }
3331
3414
  ),
3332
3415
  /* @__PURE__ */ jsxs("div", { children: [
@@ -3493,6 +3576,18 @@ function ProductDetail({
3493
3576
  onSelect: handleVariantChange
3494
3577
  }
3495
3578
  ),
3579
+ product.metadata?.sizing_notes && /* @__PURE__ */ jsx(
3580
+ "div",
3581
+ {
3582
+ style: {
3583
+ marginTop: "0.5rem",
3584
+ fontSize: "0.8125rem",
3585
+ color: "var(--commerce-text-secondary, #6b7280)",
3586
+ fontStyle: "italic"
3587
+ },
3588
+ children: product.metadata.sizing_notes
3589
+ }
3590
+ ),
3496
3591
  product.size_chart?.fit_note && /* @__PURE__ */ jsxs(
3497
3592
  "div",
3498
3593
  {
@@ -3520,6 +3615,34 @@ function ProductDetail({
3520
3615
  ]
3521
3616
  }
3522
3617
  ),
3618
+ product.metadata?.notices?.map((notice, idx) => /* @__PURE__ */ jsxs(
3619
+ "div",
3620
+ {
3621
+ style: {
3622
+ marginTop: "0.75rem",
3623
+ padding: "0.625rem 0.875rem",
3624
+ borderRadius: "var(--commerce-radius-sm, 8px)",
3625
+ backgroundColor: "color-mix(in srgb, var(--commerce-warning, #d97706) 10%, transparent)",
3626
+ border: "1px solid color-mix(in srgb, var(--commerce-warning, #d97706) 25%, transparent)",
3627
+ display: "flex",
3628
+ alignItems: "flex-start",
3629
+ gap: "0.5rem",
3630
+ fontSize: "0.8125rem",
3631
+ color: "var(--commerce-warning, #d97706)",
3632
+ fontWeight: 500,
3633
+ lineHeight: 1.4
3634
+ },
3635
+ children: [
3636
+ /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { flexShrink: 0, marginTop: 1 }, children: [
3637
+ /* @__PURE__ */ jsx("path", { d: "M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z" }),
3638
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "9", x2: "12", y2: "13" }),
3639
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
3640
+ ] }),
3641
+ notice
3642
+ ]
3643
+ },
3644
+ idx
3645
+ )),
3523
3646
  showSizeChart && product.is_clothing && product.size_chart && /* @__PURE__ */ jsx("div", { style: { marginTop: "0.625rem" }, children: /* @__PURE__ */ jsx(
3524
3647
  SizeChart,
3525
3648
  {
@@ -3582,6 +3705,35 @@ function ProductDetail({
3582
3705
  }
3583
3706
  ),
3584
3707
  showTrustBadges && /* @__PURE__ */ jsx(TrustBadges, { badges: trustBadges }),
3708
+ product.metadata?.care_instructions && /* @__PURE__ */ jsxs(
3709
+ "div",
3710
+ {
3711
+ style: {
3712
+ marginTop: "1rem",
3713
+ padding: "0.75rem 1rem",
3714
+ borderRadius: "var(--commerce-radius-sm, 8px)",
3715
+ backgroundColor: "color-mix(in srgb, var(--commerce-accent, #2563eb) 6%, transparent)",
3716
+ border: "1px solid color-mix(in srgb, var(--commerce-accent, #2563eb) 18%, transparent)",
3717
+ display: "flex",
3718
+ alignItems: "flex-start",
3719
+ gap: "0.625rem",
3720
+ fontSize: "0.8125rem",
3721
+ color: "var(--commerce-text-secondary, #6b7280)",
3722
+ lineHeight: 1.5
3723
+ },
3724
+ children: [
3725
+ /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "var(--commerce-accent, #2563eb)", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { flexShrink: 0, marginTop: 2 }, children: [
3726
+ /* @__PURE__ */ jsx("path", { d: "M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z" }),
3727
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "16", x2: "12", y2: "12" }),
3728
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "8", x2: "12.01", y2: "8" })
3729
+ ] }),
3730
+ /* @__PURE__ */ jsxs("div", { children: [
3731
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 600, color: "var(--commerce-text, #111827)", marginBottom: 2 }, children: "Care Instructions" }),
3732
+ product.metadata.care_instructions
3733
+ ] })
3734
+ ]
3735
+ }
3736
+ ),
3585
3737
  /* @__PURE__ */ jsx(
3586
3738
  ProductTabs,
3587
3739
  {
@@ -4320,6 +4472,9 @@ function CheckoutForm({
4320
4472
  name: "",
4321
4473
  phone: ""
4322
4474
  });
4475
+ const [discountCode, setDiscountCode] = useState("");
4476
+ const [discountResult, setDiscountResult] = useState(null);
4477
+ const [applyingDiscount, setApplyingDiscount] = useState(false);
4323
4478
  const isProduct = offering.type === "product";
4324
4479
  const analytics = useAnalyticsOptional();
4325
4480
  useEffect(() => {
@@ -4334,7 +4489,30 @@ function CheckoutForm({
4334
4489
  const defaultSubmitText = actualMode === "register" ? "Register" : isFree ? "Complete Order" : "Continue to Payment";
4335
4490
  const subtotal = offering.price ? offering.price * quantity : 0;
4336
4491
  const shippingCost = selectedRate ? parseFloat(selectedRate.amount) : 0;
4337
- const total = subtotal + shippingCost;
4492
+ const discountAmount = discountResult?.valid ? discountResult.discount_amount ?? 0 : 0;
4493
+ const total = Math.max(0, subtotal + shippingCost - discountAmount);
4494
+ const handleApplyDiscount = async () => {
4495
+ if (!discountCode.trim()) return;
4496
+ setApplyingDiscount(true);
4497
+ try {
4498
+ const result = await validateDiscountCode(discountCode.trim(), subtotal, [offering.id]);
4499
+ setDiscountResult(result);
4500
+ if (!result.valid) {
4501
+ setError(result.message || "Invalid discount code");
4502
+ } else {
4503
+ setError(null);
4504
+ }
4505
+ } catch {
4506
+ setDiscountResult(null);
4507
+ setError("Could not validate discount code");
4508
+ } finally {
4509
+ setApplyingDiscount(false);
4510
+ }
4511
+ };
4512
+ const handleRemoveDiscount = () => {
4513
+ setDiscountCode("");
4514
+ setDiscountResult(null);
4515
+ };
4338
4516
  const updateShippingAddress = (field, value) => {
4339
4517
  setShippingAddress((prev) => ({ ...prev, [field]: value }));
4340
4518
  };
@@ -4386,7 +4564,8 @@ function CheckoutForm({
4386
4564
  variantId,
4387
4565
  scheduleId,
4388
4566
  quantity,
4389
- customer
4567
+ customer,
4568
+ discountCode: discountResult?.valid ? discountCode.trim() : void 0
4390
4569
  };
4391
4570
  if (shippingEnabled && shippingAddress.street1 && selectedRate) {
4392
4571
  checkoutOptions.shippingAddress = {
@@ -4465,6 +4644,103 @@ function CheckoutForm({
4465
4644
  /* @__PURE__ */ jsx("span", { style: { marginLeft: "auto", fontWeight: 600 }, children: shippingEnabled && selectedRate ? `Subtotal: ${formatPrice(subtotal, offering.currency)}` : `Total: ${formatPrice(total, offering.currency)}` })
4466
4645
  ] })
4467
4646
  ] }),
4647
+ !isFree && /* @__PURE__ */ jsx("div", { style: { marginBottom: "1.5rem" }, children: discountResult?.valid ? /* @__PURE__ */ jsxs("div", { style: {
4648
+ display: "flex",
4649
+ alignItems: "center",
4650
+ justifyContent: "space-between",
4651
+ padding: "0.625rem 0.875rem",
4652
+ background: "#f0fdf4",
4653
+ border: "1px solid #bbf7d0",
4654
+ borderRadius: "6px"
4655
+ }, children: [
4656
+ /* @__PURE__ */ jsxs("div", { children: [
4657
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "0.875rem", fontWeight: 500, color: "#166534" }, children: discountResult.name || discountCode }),
4658
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: "0.875rem", color: "#15803d", marginLeft: "0.5rem" }, children: [
4659
+ "\u2212",
4660
+ formatPrice(discountAmount, offering.currency)
4661
+ ] })
4662
+ ] }),
4663
+ /* @__PURE__ */ jsx(
4664
+ "button",
4665
+ {
4666
+ type: "button",
4667
+ onClick: handleRemoveDiscount,
4668
+ style: {
4669
+ background: "none",
4670
+ border: "none",
4671
+ color: "#dc2626",
4672
+ cursor: "pointer",
4673
+ fontSize: "0.75rem",
4674
+ fontWeight: 500
4675
+ },
4676
+ children: "Remove"
4677
+ }
4678
+ )
4679
+ ] }) : /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.5rem" }, children: [
4680
+ /* @__PURE__ */ jsx(
4681
+ "input",
4682
+ {
4683
+ type: "text",
4684
+ value: discountCode,
4685
+ onChange: (e) => setDiscountCode(e.target.value.toUpperCase()),
4686
+ placeholder: "Discount code",
4687
+ className: inputClassName,
4688
+ style: {
4689
+ flex: 1,
4690
+ padding: "0.5rem 0.75rem",
4691
+ borderRadius: "6px",
4692
+ border: "1px solid #d1d5db",
4693
+ fontSize: "0.875rem"
4694
+ }
4695
+ }
4696
+ ),
4697
+ /* @__PURE__ */ jsx(
4698
+ "button",
4699
+ {
4700
+ type: "button",
4701
+ onClick: handleApplyDiscount,
4702
+ disabled: applyingDiscount || !discountCode.trim(),
4703
+ style: {
4704
+ padding: "0.5rem 1rem",
4705
+ borderRadius: "6px",
4706
+ border: "1px solid #d1d5db",
4707
+ background: "white",
4708
+ fontSize: "0.875rem",
4709
+ fontWeight: 500,
4710
+ cursor: applyingDiscount || !discountCode.trim() ? "not-allowed" : "pointer",
4711
+ opacity: applyingDiscount || !discountCode.trim() ? 0.5 : 1
4712
+ },
4713
+ children: applyingDiscount ? "..." : "Apply"
4714
+ }
4715
+ )
4716
+ ] }) }),
4717
+ discountResult?.valid && !isFree && /* @__PURE__ */ jsxs("div", { style: {
4718
+ padding: "0.75rem 1rem",
4719
+ background: "#f9fafb",
4720
+ borderRadius: "6px",
4721
+ marginBottom: "1.5rem",
4722
+ fontSize: "0.875rem"
4723
+ }, children: [
4724
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: "0.25rem" }, children: [
4725
+ /* @__PURE__ */ jsx("span", { style: { color: "#666" }, children: "Subtotal" }),
4726
+ /* @__PURE__ */ jsx("span", { children: formatPrice(subtotal, offering.currency) })
4727
+ ] }),
4728
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: "0.25rem", color: "#16a34a" }, children: [
4729
+ /* @__PURE__ */ jsx("span", { children: "Discount" }),
4730
+ /* @__PURE__ */ jsxs("span", { children: [
4731
+ "\u2212",
4732
+ formatPrice(discountAmount, offering.currency)
4733
+ ] })
4734
+ ] }),
4735
+ shippingCost > 0 && /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: "0.25rem" }, children: [
4736
+ /* @__PURE__ */ jsx("span", { style: { color: "#666" }, children: "Shipping" }),
4737
+ /* @__PURE__ */ jsx("span", { children: formatPrice(shippingCost, offering.currency) })
4738
+ ] }),
4739
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", borderTop: "1px solid #e5e7eb", paddingTop: "0.5rem", marginTop: "0.25rem", fontWeight: 600 }, children: [
4740
+ /* @__PURE__ */ jsx("span", { children: "Total" }),
4741
+ /* @__PURE__ */ jsx("span", { children: formatPrice(total, offering.currency) })
4742
+ ] })
4743
+ ] }),
4468
4744
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "1rem" }, children: [
4469
4745
  /* @__PURE__ */ jsxs("div", { children: [
4470
4746
  /* @__PURE__ */ jsx("label", { style: { display: "block", fontSize: "0.875rem", fontWeight: 500, marginBottom: "0.25rem" }, children: "Full Name *" }),
@@ -6573,6 +6849,6 @@ function getApiKey2() {
6573
6849
  return "";
6574
6850
  }
6575
6851
 
6576
- export { CalendarView, CheckoutForm, EventCalendar, EventEmbed, EventModal, EventTile, EventsWidget, OfferingCard, OfferingList, ProductDetail, ProductEmbed, ProductGrid, ProductPage, RegistrationForm, SizeChart, UpcomingEvents, createCheckoutSession, createPaymentIntent, fetchActiveProcessor, fetchCategories, fetchLatestOffering, fetchNextEvent, fetchOffering, fetchOfferings, fetchProcessorConfig, fetchProductBySlug, fetchProducts, fetchProductsPublic, fetchServices, fetchShippingRates, fetchUpcomingEvents, formatDate, formatDateRange, formatDateTime, formatPrice, formatTime, getOfferingUrl, getRelativeTimeUntil, getSpotsRemaining, isEventSoldOut, registerForEvent, useEventModal, validateAddress };
6577
- //# sourceMappingURL=chunk-LDK2GBEZ.mjs.map
6578
- //# sourceMappingURL=chunk-LDK2GBEZ.mjs.map
6852
+ export { CalendarView, CheckoutForm, EventCalendar, EventEmbed, EventModal, EventTile, EventsWidget, OfferingCard, OfferingList, ProductDetail, ProductEmbed, ProductGrid, ProductPage, RegistrationForm, SizeChart, UpcomingEvents, createCheckoutSession, createPaymentIntent, fetchActiveProcessor, fetchCategories, fetchLatestOffering, fetchNextEvent, fetchOffering, fetchOfferings, fetchProcessorConfig, fetchProductBySlug, fetchProducts, fetchProductsPublic, fetchServices, fetchShippingRates, fetchUpcomingEvents, formatDate, formatDateRange, formatDateTime, formatPrice, formatTime, getOfferingUrl, getRelativeTimeUntil, getSpotsRemaining, isEventSoldOut, registerForEvent, useEventModal, validateAddress, validateDiscountCode };
6853
+ //# sourceMappingURL=chunk-EG2FNRKR.mjs.map
6854
+ //# sourceMappingURL=chunk-EG2FNRKR.mjs.map