@cimplify/sdk 0.27.0 → 0.29.0

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.
package/dist/react.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  "use client";
2
- import { DURATION_UNIT, getVariantDisplayName, PRODUCT_TYPE, createCimplifyClient, RENDER_HINT } from './chunk-MISITC47.mjs';
3
- export { getVariantDisplayName } from './chunk-MISITC47.mjs';
2
+ import { DURATION_UNIT, getVariantDisplayName, INPUT_FIELD_TYPE, PRODUCT_TYPE, createCimplifyClient, RENDER_HINT } from './chunk-REXW2PET.mjs';
3
+ export { getVariantDisplayName } from './chunk-REXW2PET.mjs';
4
4
  import { ELEMENT_TYPES, EVENT_TYPES } from './chunk-3J4PPSMI.mjs';
5
5
  import { isSupportedCurrency, formatPrice, parsePrice, getUnitPriceAtQuantity, isOnSale, getDiscountPercentage, getBasePrice, getPriceRange, formatPriceRange } from './chunk-E3HC2YAE.mjs';
6
- import React3, { createContext, useRef, useState, useCallback, useEffect, useContext, useMemo, useSyncExternalStore, useId } from 'react';
6
+ import React10, { createContext, useRef, useState, useCallback, useEffect, useContext, useMemo, useSyncExternalStore, useId } from 'react';
7
7
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
8
8
  import { clsx } from 'clsx';
9
9
  import { twMerge } from 'tailwind-merge';
@@ -565,7 +565,8 @@ function mapServerCart(serverCart) {
565
565
  confirmationCode: item.confirmation_code || void 0,
566
566
  serviceStatus: item.service_status || void 0,
567
567
  bundleResolved: item.bundle_resolved || void 0,
568
- compositeResolved: item.composite_resolved || void 0
568
+ compositeResolved: item.composite_resolved || void 0,
569
+ customerInputs: item.customer_inputs || void 0
569
570
  });
570
571
  });
571
572
  return {
@@ -785,7 +786,8 @@ function createCartStore(params) {
785
786
  scheduled_start: normalizedOptions.scheduledStart,
786
787
  scheduled_end: normalizedOptions.scheduledEnd,
787
788
  staff_id: normalizedOptions.staffId,
788
- resource_id: normalizedOptions.resourceId
789
+ resource_id: normalizedOptions.resourceId,
790
+ customer_inputs: normalizedOptions.customerInputs
789
791
  });
790
792
  if (!result.ok) {
791
793
  throw result.error;
@@ -1423,14 +1425,14 @@ function CimplifyCheckout({
1423
1425
  resolvedCartRef.current = resolvedCart;
1424
1426
  const primaryColor = appearance?.variables?.primaryColor || "#0a2540";
1425
1427
  const isDark = appearance?.theme === "dark";
1426
- const emitStatus = React3.useEffectEvent(
1428
+ const emitStatus = React10.useEffectEvent(
1427
1429
  (nextStatus, context = {}) => {
1428
1430
  setStatus(nextStatus);
1429
1431
  setStatusText(context.display_text || "");
1430
1432
  onStatusChange?.(nextStatus, context);
1431
1433
  }
1432
1434
  );
1433
- const fireError = React3.useEffectEvent(
1435
+ const fireError = React10.useEffectEvent(
1434
1436
  (error) => {
1435
1437
  onError?.(error);
1436
1438
  }
@@ -1536,7 +1538,7 @@ function CimplifyCheckout({
1536
1538
  activeCheckoutRef.current = null;
1537
1539
  };
1538
1540
  }, []);
1539
- const handleSubmit = React3.useEffectEvent(async () => {
1541
+ const handleSubmit = React10.useEffectEvent(async () => {
1540
1542
  if (isSubmitting || isInitializing || !resolvedCartId) {
1541
1543
  if (!resolvedCartId && !isInitializing) {
1542
1544
  const message = "Your cart is empty. Add items before checkout.";
@@ -2847,6 +2849,84 @@ function useOrders(options = {}) {
2847
2849
  const { data, isLoading, error, refetch } = ordersQuery(options);
2848
2850
  return { orders: data ?? [], isLoading, error, refetch };
2849
2851
  }
2852
+ function useSubscriptions(options = {}) {
2853
+ const { client } = useCimplifyClient();
2854
+ const { status, enabled = true } = options;
2855
+ const [subscriptions, setSubscriptions] = useState([]);
2856
+ const [isLoading, setIsLoading] = useState(false);
2857
+ const [error, setError] = useState(null);
2858
+ const fetch2 = useCallback(async () => {
2859
+ if (!enabled) return;
2860
+ setIsLoading(true);
2861
+ setError(null);
2862
+ try {
2863
+ const result = await client.subscriptions.list();
2864
+ if (result.ok) {
2865
+ const filtered = status ? result.value.filter((s) => s.status === status) : result.value;
2866
+ setSubscriptions(filtered);
2867
+ } else {
2868
+ setError(result.error);
2869
+ }
2870
+ } catch (e) {
2871
+ setError(e);
2872
+ } finally {
2873
+ setIsLoading(false);
2874
+ }
2875
+ }, [client, enabled, status]);
2876
+ useEffect(() => {
2877
+ void fetch2();
2878
+ }, [fetch2]);
2879
+ return { subscriptions, isLoading, error, refetch: fetch2 };
2880
+ }
2881
+ function useSubscription(id, options = {}) {
2882
+ const { client } = useCimplifyClient();
2883
+ const { enabled = true } = options;
2884
+ const normalizedId = (id || "").trim();
2885
+ const [subscription, setSubscription] = useState(null);
2886
+ const [isLoading, setIsLoading] = useState(false);
2887
+ const [error, setError] = useState(null);
2888
+ const fetch2 = useCallback(async () => {
2889
+ if (!enabled || !normalizedId) return;
2890
+ setIsLoading(true);
2891
+ setError(null);
2892
+ try {
2893
+ const result = await client.subscriptions.get(normalizedId);
2894
+ if (result.ok) {
2895
+ setSubscription(result.value);
2896
+ } else {
2897
+ setError(result.error);
2898
+ }
2899
+ } catch (e) {
2900
+ setError(e);
2901
+ } finally {
2902
+ setIsLoading(false);
2903
+ }
2904
+ }, [client, enabled, normalizedId]);
2905
+ useEffect(() => {
2906
+ void fetch2();
2907
+ }, [fetch2]);
2908
+ const cancel = useCallback(async (reason) => {
2909
+ if (!normalizedId) return;
2910
+ await client.subscriptions.cancel(normalizedId, reason);
2911
+ void fetch2();
2912
+ }, [client, normalizedId, fetch2]);
2913
+ const pause = useCallback(async () => {
2914
+ if (!normalizedId) return;
2915
+ await client.subscriptions.pause(normalizedId);
2916
+ void fetch2();
2917
+ }, [client, normalizedId, fetch2]);
2918
+ const resume = useCallback(async () => {
2919
+ if (!normalizedId) return;
2920
+ await client.subscriptions.resume(normalizedId);
2921
+ void fetch2();
2922
+ }, [client, normalizedId, fetch2]);
2923
+ const skipNext = useCallback(async () => {
2924
+ if (!normalizedId) return;
2925
+ await client.subscriptions.skipNextRenewal(normalizedId);
2926
+ void fetch2();
2927
+ }, [client, normalizedId, fetch2]);
2928
+ return { subscription, isLoading, error, cancel, pause, resume, skipNext, refetch: fetch2 };
2929
+ }
2850
2930
 
2851
2931
  // src/react/hooks/use-services.ts
2852
2932
  var servicesQuery = createCachedQuery({
@@ -4770,6 +4850,880 @@ function VolumePricing({
4770
4850
  }
4771
4851
  );
4772
4852
  }
4853
+ function CustomerInputFields({
4854
+ fields,
4855
+ values,
4856
+ onChange,
4857
+ onFileUpload,
4858
+ className,
4859
+ classNames
4860
+ }) {
4861
+ const clientContext = useOptionalCimplifyClient();
4862
+ if (fields.length === 0) return null;
4863
+ const sorted = [...fields].sort((a, b) => a.display_order - b.display_order);
4864
+ const setValue = useCallback(
4865
+ (fieldId, value) => {
4866
+ onChange({ ...values, [fieldId]: value });
4867
+ },
4868
+ [values, onChange]
4869
+ );
4870
+ const defaultFileUpload = useCallback(
4871
+ async (file) => {
4872
+ if (onFileUpload) return onFileUpload(file, {});
4873
+ if (!clientContext?.client) throw new Error("No upload provider available");
4874
+ const result = await clientContext.client.uploads.upload(file);
4875
+ if (!result.ok) throw new Error(result.error.message);
4876
+ return result.value.url;
4877
+ },
4878
+ [onFileUpload, clientContext]
4879
+ );
4880
+ return /* @__PURE__ */ jsx("div", { "data-cimplify-customer-inputs": true, className: cn("space-y-4", className, classNames?.root), children: sorted.map((field) => /* @__PURE__ */ jsx(
4881
+ InputField,
4882
+ {
4883
+ field,
4884
+ value: values[field.id],
4885
+ onValueChange: (value) => setValue(field.id, value),
4886
+ onFileUpload: onFileUpload ?? (clientContext?.client ? (_file, _field) => defaultFileUpload(_file) : void 0),
4887
+ classNames
4888
+ },
4889
+ field.id
4890
+ )) });
4891
+ }
4892
+ function InputField({
4893
+ field,
4894
+ value,
4895
+ onValueChange,
4896
+ onFileUpload,
4897
+ classNames
4898
+ }) {
4899
+ const hasPriceAdjustment = field.price_adjustment != null && parsePrice(field.price_adjustment) > 0;
4900
+ return /* @__PURE__ */ jsxs("div", { "data-cimplify-customer-input": true, "data-field-type": field.field_type, className: classNames?.field, children: [
4901
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1.5", children: [
4902
+ /* @__PURE__ */ jsx("label", { className: cn("text-sm font-semibold", classNames?.label), children: field.name }),
4903
+ field.is_required && /* @__PURE__ */ jsx("span", { className: cn("text-xs text-destructive", classNames?.required), children: "Required" }),
4904
+ hasPriceAdjustment && /* @__PURE__ */ jsxs("span", { className: cn("text-xs text-muted-foreground", classNames?.priceAdjustment), children: [
4905
+ "+",
4906
+ /* @__PURE__ */ jsx(Price, { amount: field.price_adjustment })
4907
+ ] })
4908
+ ] }),
4909
+ field.help_text && /* @__PURE__ */ jsx("p", { className: cn("text-xs text-muted-foreground mb-1.5", classNames?.helpText), children: field.help_text }),
4910
+ /* @__PURE__ */ jsx(
4911
+ FieldInput,
4912
+ {
4913
+ field,
4914
+ value,
4915
+ onValueChange,
4916
+ onFileUpload,
4917
+ classNames
4918
+ }
4919
+ )
4920
+ ] });
4921
+ }
4922
+ function FieldInput({
4923
+ field,
4924
+ value,
4925
+ onValueChange,
4926
+ onFileUpload,
4927
+ classNames
4928
+ }) {
4929
+ const inputClass = cn(
4930
+ "w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring",
4931
+ classNames?.input
4932
+ );
4933
+ switch (field.field_type) {
4934
+ case INPUT_FIELD_TYPE.Text:
4935
+ case INPUT_FIELD_TYPE.Url:
4936
+ return /* @__PURE__ */ jsx(
4937
+ "input",
4938
+ {
4939
+ type: field.field_type === INPUT_FIELD_TYPE.Url ? "url" : "text",
4940
+ value: typeof value === "string" ? value : "",
4941
+ onChange: (e) => onValueChange(e.target.value),
4942
+ placeholder: field.placeholder,
4943
+ required: field.is_required,
4944
+ maxLength: field.validation?.max_length ?? void 0,
4945
+ className: inputClass
4946
+ }
4947
+ );
4948
+ case INPUT_FIELD_TYPE.Textarea:
4949
+ return /* @__PURE__ */ jsx(
4950
+ "textarea",
4951
+ {
4952
+ value: typeof value === "string" ? value : "",
4953
+ onChange: (e) => onValueChange(e.target.value),
4954
+ placeholder: field.placeholder,
4955
+ required: field.is_required,
4956
+ maxLength: field.validation?.max_length ?? void 0,
4957
+ rows: 3,
4958
+ className: cn(inputClass, "resize-none", classNames?.textarea)
4959
+ }
4960
+ );
4961
+ case INPUT_FIELD_TYPE.Number:
4962
+ return /* @__PURE__ */ jsx(
4963
+ "input",
4964
+ {
4965
+ type: "number",
4966
+ value: typeof value === "number" ? value : "",
4967
+ onChange: (e) => onValueChange(e.target.value ? parseFloat(e.target.value) : void 0),
4968
+ placeholder: field.placeholder,
4969
+ required: field.is_required,
4970
+ min: field.validation?.min_value ?? void 0,
4971
+ max: field.validation?.max_value ?? void 0,
4972
+ className: inputClass
4973
+ }
4974
+ );
4975
+ case INPUT_FIELD_TYPE.Select:
4976
+ return /* @__PURE__ */ jsxs(
4977
+ "select",
4978
+ {
4979
+ value: typeof value === "string" ? value : "",
4980
+ onChange: (e) => onValueChange(e.target.value || void 0),
4981
+ required: field.is_required,
4982
+ className: cn(inputClass, classNames?.select),
4983
+ children: [
4984
+ /* @__PURE__ */ jsx("option", { value: "", children: field.placeholder || "Select..." }),
4985
+ (field.options || []).map((opt) => /* @__PURE__ */ jsx("option", { value: opt, children: opt }, opt))
4986
+ ]
4987
+ }
4988
+ );
4989
+ case INPUT_FIELD_TYPE.Radio:
4990
+ return /* @__PURE__ */ jsx("div", { className: cn("space-y-2", classNames?.radioGroup), children: (field.options || []).map((opt) => /* @__PURE__ */ jsxs(
4991
+ "label",
4992
+ {
4993
+ className: cn(
4994
+ "flex items-center gap-2 text-sm cursor-pointer",
4995
+ classNames?.radioOption
4996
+ ),
4997
+ children: [
4998
+ /* @__PURE__ */ jsx(
4999
+ "input",
5000
+ {
5001
+ type: "radio",
5002
+ name: field.id,
5003
+ value: opt,
5004
+ checked: value === opt,
5005
+ onChange: () => onValueChange(opt),
5006
+ className: "accent-primary"
5007
+ }
5008
+ ),
5009
+ opt
5010
+ ]
5011
+ },
5012
+ opt
5013
+ )) });
5014
+ case INPUT_FIELD_TYPE.Checkbox:
5015
+ return /* @__PURE__ */ jsxs("label", { className: cn("flex items-center gap-2 text-sm cursor-pointer", classNames?.checkboxLabel), children: [
5016
+ /* @__PURE__ */ jsx(
5017
+ "input",
5018
+ {
5019
+ type: "checkbox",
5020
+ checked: value === true,
5021
+ onChange: (e) => onValueChange(e.target.checked),
5022
+ className: "accent-primary w-4 h-4"
5023
+ }
5024
+ ),
5025
+ field.placeholder || field.name
5026
+ ] });
5027
+ case INPUT_FIELD_TYPE.Color:
5028
+ return /* @__PURE__ */ jsx(
5029
+ "input",
5030
+ {
5031
+ type: "color",
5032
+ value: typeof value === "string" ? value : "#000000",
5033
+ onChange: (e) => onValueChange(e.target.value),
5034
+ className: cn("w-12 h-10 rounded-md border border-input cursor-pointer", classNames?.colorInput)
5035
+ }
5036
+ );
5037
+ case INPUT_FIELD_TYPE.Date:
5038
+ return /* @__PURE__ */ jsx(
5039
+ "input",
5040
+ {
5041
+ type: "date",
5042
+ value: typeof value === "string" ? value : "",
5043
+ onChange: (e) => onValueChange(e.target.value || void 0),
5044
+ required: field.is_required,
5045
+ className: inputClass
5046
+ }
5047
+ );
5048
+ case INPUT_FIELD_TYPE.File:
5049
+ case INPUT_FIELD_TYPE.Image:
5050
+ return /* @__PURE__ */ jsx(
5051
+ FileUploadInput,
5052
+ {
5053
+ field,
5054
+ value,
5055
+ onValueChange,
5056
+ onFileUpload,
5057
+ classNames
5058
+ }
5059
+ );
5060
+ case INPUT_FIELD_TYPE.Email:
5061
+ return /* @__PURE__ */ jsx(
5062
+ "input",
5063
+ {
5064
+ type: "email",
5065
+ value: typeof value === "string" ? value : "",
5066
+ onChange: (e) => onValueChange(e.target.value),
5067
+ placeholder: field.placeholder || "email@example.com",
5068
+ required: field.is_required,
5069
+ className: inputClass
5070
+ }
5071
+ );
5072
+ case INPUT_FIELD_TYPE.DateTime: {
5073
+ const dtLocalValue = typeof value === "string" && value.includes("T") ? value.slice(0, 16) : typeof value === "string" ? value : "";
5074
+ return /* @__PURE__ */ jsx(
5075
+ "input",
5076
+ {
5077
+ type: "datetime-local",
5078
+ value: dtLocalValue,
5079
+ onChange: (e) => {
5080
+ if (!e.target.value) {
5081
+ onValueChange(void 0);
5082
+ return;
5083
+ }
5084
+ const date = new Date(e.target.value);
5085
+ onValueChange(Number.isNaN(date.getTime()) ? e.target.value : date.toISOString());
5086
+ },
5087
+ required: field.is_required,
5088
+ className: inputClass
5089
+ }
5090
+ );
5091
+ }
5092
+ case INPUT_FIELD_TYPE.Time:
5093
+ return /* @__PURE__ */ jsx(
5094
+ "input",
5095
+ {
5096
+ type: "time",
5097
+ value: typeof value === "string" ? value : "",
5098
+ onChange: (e) => onValueChange(e.target.value || void 0),
5099
+ required: field.is_required,
5100
+ className: inputClass
5101
+ }
5102
+ );
5103
+ case INPUT_FIELD_TYPE.MultiSelect:
5104
+ return /* @__PURE__ */ jsx(
5105
+ MultiSelectInput,
5106
+ {
5107
+ field,
5108
+ value,
5109
+ onValueChange,
5110
+ classNames
5111
+ }
5112
+ );
5113
+ case INPUT_FIELD_TYPE.DateRange:
5114
+ return /* @__PURE__ */ jsx(
5115
+ DateRangeInput,
5116
+ {
5117
+ field,
5118
+ value,
5119
+ onValueChange,
5120
+ classNames,
5121
+ inputClass
5122
+ }
5123
+ );
5124
+ case INPUT_FIELD_TYPE.Address:
5125
+ return /* @__PURE__ */ jsx(
5126
+ AddressInput,
5127
+ {
5128
+ field,
5129
+ value,
5130
+ onValueChange,
5131
+ classNames,
5132
+ inputClass
5133
+ }
5134
+ );
5135
+ case INPUT_FIELD_TYPE.Location:
5136
+ return /* @__PURE__ */ jsx(
5137
+ LocationInput,
5138
+ {
5139
+ field,
5140
+ value,
5141
+ onValueChange,
5142
+ classNames,
5143
+ inputClass
5144
+ }
5145
+ );
5146
+ case INPUT_FIELD_TYPE.Phone:
5147
+ return /* @__PURE__ */ jsx(
5148
+ PhoneInput,
5149
+ {
5150
+ field,
5151
+ value,
5152
+ onValueChange,
5153
+ classNames,
5154
+ inputClass
5155
+ }
5156
+ );
5157
+ case INPUT_FIELD_TYPE.Signature:
5158
+ return /* @__PURE__ */ jsx(
5159
+ SignatureInput,
5160
+ {
5161
+ field,
5162
+ value,
5163
+ onValueChange,
5164
+ classNames
5165
+ }
5166
+ );
5167
+ default:
5168
+ return /* @__PURE__ */ jsx(
5169
+ "input",
5170
+ {
5171
+ type: "text",
5172
+ value: typeof value === "string" ? value : "",
5173
+ onChange: (e) => onValueChange(e.target.value),
5174
+ placeholder: field.placeholder,
5175
+ className: inputClass
5176
+ }
5177
+ );
5178
+ }
5179
+ }
5180
+ function FileUploadInput({
5181
+ field,
5182
+ value,
5183
+ onValueChange,
5184
+ onFileUpload,
5185
+ classNames
5186
+ }) {
5187
+ const [isUploading, setIsUploading] = React10.useState(false);
5188
+ const fileUrl = typeof value === "string" ? value : void 0;
5189
+ const acceptedFormats = field.validation?.accepted_formats;
5190
+ const accept = acceptedFormats ? acceptedFormats.map((f) => `.${f}`).join(",") : field.field_type === INPUT_FIELD_TYPE.Image ? "image/*" : void 0;
5191
+ const handleFile = async (file) => {
5192
+ if (!onFileUpload) return;
5193
+ if (field.validation?.max_size_mb) {
5194
+ const maxBytes = field.validation.max_size_mb * 1024 * 1024;
5195
+ if (file.size > maxBytes) {
5196
+ return;
5197
+ }
5198
+ }
5199
+ setIsUploading(true);
5200
+ try {
5201
+ const url = await onFileUpload(file, field);
5202
+ onValueChange(url);
5203
+ } finally {
5204
+ setIsUploading(false);
5205
+ }
5206
+ };
5207
+ return /* @__PURE__ */ jsx("div", { className: cn("space-y-2", classNames?.fileInput), children: fileUrl ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
5208
+ field.field_type === INPUT_FIELD_TYPE.Image && /* @__PURE__ */ jsx("img", { src: fileUrl, alt: "Uploaded", className: "w-16 h-16 object-cover rounded-md border border-border" }),
5209
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-foreground truncate", children: fileUrl.split("/").pop() }) }),
5210
+ /* @__PURE__ */ jsx(
5211
+ "button",
5212
+ {
5213
+ type: "button",
5214
+ onClick: () => onValueChange(void 0),
5215
+ className: "text-xs text-muted-foreground hover:text-destructive transition-colors",
5216
+ children: "Remove"
5217
+ }
5218
+ )
5219
+ ] }) : /* @__PURE__ */ jsxs("label", { className: "flex flex-col items-center justify-center w-full h-24 border-2 border-dashed border-border rounded-lg cursor-pointer hover:border-primary/40 transition-colors", children: [
5220
+ /* @__PURE__ */ jsx(
5221
+ "input",
5222
+ {
5223
+ type: "file",
5224
+ accept,
5225
+ onChange: (e) => {
5226
+ const file = e.target.files?.[0];
5227
+ if (file) void handleFile(file);
5228
+ },
5229
+ className: "hidden",
5230
+ disabled: isUploading
5231
+ }
5232
+ ),
5233
+ isUploading ? /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "Uploading..." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
5234
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: field.placeholder || `Upload ${field.field_type === INPUT_FIELD_TYPE.Image ? "image" : "file"}` }),
5235
+ acceptedFormats && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground/60 mt-1", children: [
5236
+ acceptedFormats.map((f) => f.toUpperCase()).join(", "),
5237
+ field.validation?.max_size_mb && ` \xB7 Max ${field.validation.max_size_mb}MB`
5238
+ ] })
5239
+ ] })
5240
+ ] }) });
5241
+ }
5242
+ function MultiSelectInput({
5243
+ field,
5244
+ value,
5245
+ onValueChange,
5246
+ classNames
5247
+ }) {
5248
+ const selected = Array.isArray(value) ? value : [];
5249
+ const options = field.options || [];
5250
+ const maxSelections = field.validation?.max_selections;
5251
+ const toggle = (opt) => {
5252
+ const next = selected.includes(opt) ? selected.filter((s) => s !== opt) : maxSelections && selected.length >= maxSelections ? selected : [...selected, opt];
5253
+ onValueChange(next.length > 0 ? next : void 0);
5254
+ };
5255
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", classNames?.multiSelectGroup), children: [
5256
+ options.map((opt) => /* @__PURE__ */ jsxs(
5257
+ "label",
5258
+ {
5259
+ className: cn(
5260
+ "flex items-center gap-2 text-sm cursor-pointer",
5261
+ classNames?.multiSelectOption
5262
+ ),
5263
+ children: [
5264
+ /* @__PURE__ */ jsx(
5265
+ "input",
5266
+ {
5267
+ type: "checkbox",
5268
+ checked: selected.includes(opt),
5269
+ onChange: () => toggle(opt),
5270
+ className: "accent-primary w-4 h-4"
5271
+ }
5272
+ ),
5273
+ opt
5274
+ ]
5275
+ },
5276
+ opt
5277
+ )),
5278
+ maxSelections && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
5279
+ selected.length,
5280
+ "/",
5281
+ maxSelections,
5282
+ " selected"
5283
+ ] })
5284
+ ] });
5285
+ }
5286
+ function DateRangeInput({
5287
+ field,
5288
+ value,
5289
+ onValueChange,
5290
+ classNames,
5291
+ inputClass
5292
+ }) {
5293
+ const range = value && typeof value === "object" ? value : {};
5294
+ const update = (key, v) => {
5295
+ const next = { ...range, [key]: v };
5296
+ onValueChange(next.start || next.end ? next : void 0);
5297
+ };
5298
+ return /* @__PURE__ */ jsxs("div", { className: cn("grid grid-cols-2 gap-3", classNames?.dateRangeInput), children: [
5299
+ /* @__PURE__ */ jsxs("div", { children: [
5300
+ /* @__PURE__ */ jsx("label", { className: "text-xs text-muted-foreground mb-1 block", children: "Start" }),
5301
+ /* @__PURE__ */ jsx(
5302
+ "input",
5303
+ {
5304
+ type: "date",
5305
+ value: range.start || "",
5306
+ onChange: (e) => update("start", e.target.value),
5307
+ required: field.is_required,
5308
+ className: inputClass
5309
+ }
5310
+ )
5311
+ ] }),
5312
+ /* @__PURE__ */ jsxs("div", { children: [
5313
+ /* @__PURE__ */ jsx("label", { className: "text-xs text-muted-foreground mb-1 block", children: "End" }),
5314
+ /* @__PURE__ */ jsx(
5315
+ "input",
5316
+ {
5317
+ type: "date",
5318
+ value: range.end || "",
5319
+ onChange: (e) => update("end", e.target.value),
5320
+ required: field.is_required,
5321
+ className: inputClass
5322
+ }
5323
+ )
5324
+ ] })
5325
+ ] });
5326
+ }
5327
+ function AddressInput({
5328
+ field,
5329
+ value,
5330
+ onValueChange,
5331
+ classNames,
5332
+ inputClass
5333
+ }) {
5334
+ const clientContext = useOptionalCimplifyClient();
5335
+ const addr = value && typeof value === "object" ? value : null;
5336
+ const [inputText, setInputText] = React10.useState(addr?.formatted_address ?? "");
5337
+ const [suggestions, setSuggestions] = React10.useState([]);
5338
+ const [isOpen, setIsOpen] = React10.useState(false);
5339
+ const [highlightedIndex, setHighlightedIndex] = React10.useState(-1);
5340
+ const debounceRef = React10.useRef(null);
5341
+ const seqRef = React10.useRef(0);
5342
+ const sessionTokenRef = React10.useRef(createSessionToken());
5343
+ const blurTimeoutRef = React10.useRef(null);
5344
+ React10.useEffect(() => {
5345
+ return () => {
5346
+ if (debounceRef.current) clearTimeout(debounceRef.current);
5347
+ if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
5348
+ seqRef.current += 1;
5349
+ };
5350
+ }, []);
5351
+ const fetchSuggestions = React10.useCallback(async (query) => {
5352
+ const client = clientContext?.client;
5353
+ if (!client || query.length < 3) {
5354
+ setSuggestions([]);
5355
+ setIsOpen(false);
5356
+ return;
5357
+ }
5358
+ const seq = ++seqRef.current;
5359
+ const result = await client.places.autocomplete(query, sessionTokenRef.current);
5360
+ if (seqRef.current !== seq) return;
5361
+ if (result.ok && result.value.predictions.length > 0) {
5362
+ setSuggestions(result.value.predictions);
5363
+ setHighlightedIndex(0);
5364
+ setIsOpen(true);
5365
+ } else {
5366
+ setSuggestions([]);
5367
+ setIsOpen(false);
5368
+ }
5369
+ }, [clientContext]);
5370
+ const handleInputChange = (nextValue) => {
5371
+ setInputText(nextValue);
5372
+ if (debounceRef.current) clearTimeout(debounceRef.current);
5373
+ debounceRef.current = setTimeout(() => void fetchSuggestions(nextValue.trim()), 300);
5374
+ };
5375
+ const selectSuggestion = React10.useCallback(async (suggestion) => {
5376
+ if (debounceRef.current) clearTimeout(debounceRef.current);
5377
+ if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
5378
+ const fallback = suggestion.description.split(",")[0]?.trim() || suggestion.description;
5379
+ setInputText(fallback);
5380
+ setSuggestions([]);
5381
+ setIsOpen(false);
5382
+ const client = clientContext?.client;
5383
+ if (!client) return;
5384
+ const result = await client.places.details(suggestion.place_id, sessionTokenRef.current);
5385
+ sessionTokenRef.current = createSessionToken();
5386
+ if (result.ok) {
5387
+ const d = result.value;
5388
+ const addressValue = {
5389
+ formatted_address: d.formatted_address || fallback,
5390
+ street_address: d.street_address,
5391
+ apartment: d.apartment,
5392
+ city: d.city,
5393
+ region: d.region,
5394
+ postal_code: d.postal_code,
5395
+ country: d.country,
5396
+ latitude: d.latitude,
5397
+ longitude: d.longitude,
5398
+ place_id: d.place_id
5399
+ };
5400
+ setInputText(addressValue.formatted_address);
5401
+ onValueChange(addressValue);
5402
+ }
5403
+ }, [clientContext, onValueChange]);
5404
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2 relative", classNames?.addressInput), children: [
5405
+ /* @__PURE__ */ jsx(
5406
+ "input",
5407
+ {
5408
+ type: "text",
5409
+ value: inputText,
5410
+ onChange: (e) => handleInputChange(e.target.value),
5411
+ onFocus: () => {
5412
+ if (suggestions.length > 0) setIsOpen(true);
5413
+ },
5414
+ onBlur: () => {
5415
+ blurTimeoutRef.current = setTimeout(() => setIsOpen(false), 120);
5416
+ },
5417
+ onKeyDown: (e) => {
5418
+ if (e.key === "Escape") {
5419
+ setIsOpen(false);
5420
+ return;
5421
+ }
5422
+ if (!isOpen || suggestions.length === 0) return;
5423
+ if (e.key === "ArrowDown") {
5424
+ e.preventDefault();
5425
+ setHighlightedIndex((i) => i < suggestions.length - 1 ? i + 1 : 0);
5426
+ } else if (e.key === "ArrowUp") {
5427
+ e.preventDefault();
5428
+ setHighlightedIndex((i) => i > 0 ? i - 1 : suggestions.length - 1);
5429
+ } else if (e.key === "Enter" && highlightedIndex >= 0) {
5430
+ e.preventDefault();
5431
+ void selectSuggestion(suggestions[highlightedIndex]);
5432
+ }
5433
+ },
5434
+ placeholder: field.placeholder || "Search for an address...",
5435
+ required: field.is_required,
5436
+ autoComplete: "off",
5437
+ "aria-autocomplete": "list",
5438
+ "aria-expanded": isOpen && suggestions.length > 0,
5439
+ className: inputClass
5440
+ }
5441
+ ),
5442
+ isOpen && suggestions.length > 0 && /* @__PURE__ */ jsx("div", { role: "listbox", className: "absolute left-0 right-0 top-full z-20 mt-1 overflow-hidden rounded-md border border-border bg-background shadow-lg", children: suggestions.map((s, i) => /* @__PURE__ */ jsx(
5443
+ "button",
5444
+ {
5445
+ type: "button",
5446
+ role: "option",
5447
+ "aria-selected": i === highlightedIndex,
5448
+ onMouseDown: (e) => {
5449
+ e.preventDefault();
5450
+ void selectSuggestion(s);
5451
+ },
5452
+ className: cn("block w-full px-3 py-2.5 text-left text-sm transition-colors", i === highlightedIndex ? "bg-muted text-foreground" : "text-foreground hover:bg-muted/70"),
5453
+ children: s.description
5454
+ },
5455
+ s.place_id
5456
+ )) }),
5457
+ addr && /* @__PURE__ */ jsx(
5458
+ "input",
5459
+ {
5460
+ type: "text",
5461
+ value: addr.apartment || "",
5462
+ onChange: (e) => onValueChange({ ...addr, apartment: e.target.value || void 0 }),
5463
+ placeholder: "Apt, suite, unit (optional)",
5464
+ className: inputClass
5465
+ }
5466
+ )
5467
+ ] });
5468
+ }
5469
+ function LocationInput({
5470
+ field,
5471
+ value,
5472
+ onValueChange,
5473
+ classNames,
5474
+ inputClass
5475
+ }) {
5476
+ const clientContext = useOptionalCimplifyClient();
5477
+ const loc = value && typeof value === "object" ? value : null;
5478
+ const [inputText, setInputText] = React10.useState(loc?.label ?? "");
5479
+ const [suggestions, setSuggestions] = React10.useState([]);
5480
+ const [isOpen, setIsOpen] = React10.useState(false);
5481
+ const [highlightedIndex, setHighlightedIndex] = React10.useState(-1);
5482
+ const debounceRef = React10.useRef(null);
5483
+ const seqRef = React10.useRef(0);
5484
+ const sessionTokenRef = React10.useRef(createSessionToken());
5485
+ const blurTimeoutRef = React10.useRef(null);
5486
+ React10.useEffect(() => {
5487
+ return () => {
5488
+ if (debounceRef.current) clearTimeout(debounceRef.current);
5489
+ if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
5490
+ seqRef.current += 1;
5491
+ };
5492
+ }, []);
5493
+ const fetchSuggestions = React10.useCallback(async (query) => {
5494
+ const client = clientContext?.client;
5495
+ if (!client || query.length < 3) {
5496
+ setSuggestions([]);
5497
+ setIsOpen(false);
5498
+ return;
5499
+ }
5500
+ const seq = ++seqRef.current;
5501
+ const result = await client.places.autocomplete(query, sessionTokenRef.current);
5502
+ if (seqRef.current !== seq) return;
5503
+ if (result.ok && result.value.predictions.length > 0) {
5504
+ setSuggestions(result.value.predictions);
5505
+ setHighlightedIndex(0);
5506
+ setIsOpen(true);
5507
+ } else {
5508
+ setSuggestions([]);
5509
+ setIsOpen(false);
5510
+ }
5511
+ }, [clientContext]);
5512
+ const handleInputChange = (nextValue) => {
5513
+ setInputText(nextValue);
5514
+ if (debounceRef.current) clearTimeout(debounceRef.current);
5515
+ debounceRef.current = setTimeout(() => void fetchSuggestions(nextValue.trim()), 300);
5516
+ };
5517
+ const selectSuggestion = React10.useCallback(async (suggestion) => {
5518
+ if (debounceRef.current) clearTimeout(debounceRef.current);
5519
+ if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
5520
+ const label = suggestion.description.split(",")[0]?.trim() || suggestion.description;
5521
+ setInputText(label);
5522
+ setSuggestions([]);
5523
+ setIsOpen(false);
5524
+ const client = clientContext?.client;
5525
+ if (!client) return;
5526
+ const result = await client.places.details(suggestion.place_id, sessionTokenRef.current);
5527
+ sessionTokenRef.current = createSessionToken();
5528
+ if (result.ok) {
5529
+ const d = result.value;
5530
+ onValueChange({
5531
+ latitude: d.latitude,
5532
+ longitude: d.longitude,
5533
+ label: d.formatted_address || label
5534
+ });
5535
+ setInputText(d.formatted_address || label);
5536
+ }
5537
+ }, [clientContext, onValueChange]);
5538
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative", classNames?.locationInput), children: [
5539
+ /* @__PURE__ */ jsx(
5540
+ "input",
5541
+ {
5542
+ type: "text",
5543
+ value: inputText,
5544
+ onChange: (e) => handleInputChange(e.target.value),
5545
+ onFocus: () => {
5546
+ if (suggestions.length > 0) setIsOpen(true);
5547
+ },
5548
+ onBlur: () => {
5549
+ blurTimeoutRef.current = setTimeout(() => setIsOpen(false), 120);
5550
+ },
5551
+ onKeyDown: (e) => {
5552
+ if (e.key === "Escape") {
5553
+ setIsOpen(false);
5554
+ return;
5555
+ }
5556
+ if (!isOpen || suggestions.length === 0) return;
5557
+ if (e.key === "ArrowDown") {
5558
+ e.preventDefault();
5559
+ setHighlightedIndex((i) => i < suggestions.length - 1 ? i + 1 : 0);
5560
+ } else if (e.key === "ArrowUp") {
5561
+ e.preventDefault();
5562
+ setHighlightedIndex((i) => i > 0 ? i - 1 : suggestions.length - 1);
5563
+ } else if (e.key === "Enter" && highlightedIndex >= 0) {
5564
+ e.preventDefault();
5565
+ void selectSuggestion(suggestions[highlightedIndex]);
5566
+ }
5567
+ },
5568
+ placeholder: field.placeholder || "Search for a location...",
5569
+ required: field.is_required,
5570
+ autoComplete: "off",
5571
+ "aria-autocomplete": "list",
5572
+ "aria-expanded": isOpen && suggestions.length > 0,
5573
+ className: inputClass
5574
+ }
5575
+ ),
5576
+ isOpen && suggestions.length > 0 && /* @__PURE__ */ jsx("div", { role: "listbox", className: "absolute left-0 right-0 top-full z-20 mt-1 overflow-hidden rounded-md border border-border bg-background shadow-lg", children: suggestions.map((s, i) => /* @__PURE__ */ jsx(
5577
+ "button",
5578
+ {
5579
+ type: "button",
5580
+ role: "option",
5581
+ "aria-selected": i === highlightedIndex,
5582
+ onMouseDown: (e) => {
5583
+ e.preventDefault();
5584
+ void selectSuggestion(s);
5585
+ },
5586
+ className: cn("block w-full px-3 py-2.5 text-left text-sm transition-colors", i === highlightedIndex ? "bg-muted text-foreground" : "text-foreground hover:bg-muted/70"),
5587
+ children: s.description
5588
+ },
5589
+ s.place_id
5590
+ )) }),
5591
+ loc && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-1", children: [
5592
+ loc.latitude.toFixed(4),
5593
+ ", ",
5594
+ loc.longitude.toFixed(4)
5595
+ ] })
5596
+ ] });
5597
+ }
5598
+ function PhoneInput({
5599
+ field,
5600
+ value,
5601
+ onValueChange,
5602
+ classNames,
5603
+ inputClass
5604
+ }) {
5605
+ const phone = value && typeof value === "object" ? value : null;
5606
+ const [code, setCode] = React10.useState(phone?.country_code ?? "+1");
5607
+ const [number, setNumber] = React10.useState(phone?.number ?? "");
5608
+ const update = (nextCode, nextNumber) => {
5609
+ setCode(nextCode);
5610
+ setNumber(nextNumber);
5611
+ if (nextNumber) {
5612
+ onValueChange({
5613
+ country_code: nextCode,
5614
+ number: nextNumber,
5615
+ formatted: `${nextCode} ${nextNumber}`
5616
+ });
5617
+ } else {
5618
+ onValueChange(void 0);
5619
+ }
5620
+ };
5621
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex gap-2", classNames?.phoneInput), children: [
5622
+ /* @__PURE__ */ jsx(
5623
+ "input",
5624
+ {
5625
+ type: "text",
5626
+ value: code,
5627
+ onChange: (e) => update(e.target.value, number),
5628
+ placeholder: "+1",
5629
+ className: cn(inputClass, "w-20 shrink-0")
5630
+ }
5631
+ ),
5632
+ /* @__PURE__ */ jsx(
5633
+ "input",
5634
+ {
5635
+ type: "tel",
5636
+ value: number,
5637
+ onChange: (e) => update(code, e.target.value),
5638
+ placeholder: field.placeholder || "Phone number",
5639
+ required: field.is_required,
5640
+ className: inputClass
5641
+ }
5642
+ )
5643
+ ] });
5644
+ }
5645
+ function SignatureInput({
5646
+ field,
5647
+ value,
5648
+ onValueChange,
5649
+ classNames
5650
+ }) {
5651
+ const sig = value && typeof value === "object" ? value : null;
5652
+ const canvasRef = React10.useRef(null);
5653
+ const isDrawing = React10.useRef(false);
5654
+ const getCtx = () => canvasRef.current?.getContext("2d") ?? null;
5655
+ const startDraw = (e) => {
5656
+ const ctx = getCtx();
5657
+ if (!ctx) return;
5658
+ isDrawing.current = true;
5659
+ const rect = canvasRef.current.getBoundingClientRect();
5660
+ ctx.beginPath();
5661
+ ctx.moveTo(e.clientX - rect.left, e.clientY - rect.top);
5662
+ canvasRef.current.setPointerCapture(e.pointerId);
5663
+ };
5664
+ const draw = (e) => {
5665
+ if (!isDrawing.current) return;
5666
+ const ctx = getCtx();
5667
+ if (!ctx) return;
5668
+ const rect = canvasRef.current.getBoundingClientRect();
5669
+ ctx.lineWidth = 2;
5670
+ ctx.lineCap = "round";
5671
+ const style = getComputedStyle(canvasRef.current);
5672
+ ctx.strokeStyle = style.color || "#000";
5673
+ ctx.lineTo(e.clientX - rect.left, e.clientY - rect.top);
5674
+ ctx.stroke();
5675
+ };
5676
+ const endDraw = () => {
5677
+ if (!isDrawing.current) return;
5678
+ isDrawing.current = false;
5679
+ const canvas = canvasRef.current;
5680
+ if (!canvas) return;
5681
+ onValueChange({
5682
+ data_url: canvas.toDataURL("image/png"),
5683
+ signer_name: sig?.signer_name
5684
+ });
5685
+ };
5686
+ const clear = () => {
5687
+ const canvas = canvasRef.current;
5688
+ if (!canvas) return;
5689
+ const ctx = canvas.getContext("2d");
5690
+ if (ctx) ctx.clearRect(0, 0, canvas.width, canvas.height);
5691
+ onValueChange(void 0);
5692
+ };
5693
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", classNames?.signatureCanvas), children: [
5694
+ /* @__PURE__ */ jsxs("div", { className: "relative rounded-md border border-input overflow-hidden", children: [
5695
+ /* @__PURE__ */ jsx(
5696
+ "canvas",
5697
+ {
5698
+ ref: canvasRef,
5699
+ width: 400,
5700
+ height: 150,
5701
+ className: "w-full touch-none cursor-crosshair bg-background text-foreground",
5702
+ onPointerDown: startDraw,
5703
+ onPointerMove: draw,
5704
+ onPointerUp: endDraw,
5705
+ onPointerLeave: endDraw
5706
+ }
5707
+ ),
5708
+ sig?.data_url && /* @__PURE__ */ jsx(
5709
+ "button",
5710
+ {
5711
+ type: "button",
5712
+ onClick: clear,
5713
+ className: "absolute top-1.5 right-1.5 text-xs text-muted-foreground hover:text-destructive transition-colors",
5714
+ children: "Clear"
5715
+ }
5716
+ )
5717
+ ] }),
5718
+ !sig?.data_url && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: field.placeholder || "Draw your signature above" })
5719
+ ] });
5720
+ }
5721
+ function createSessionToken() {
5722
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
5723
+ return crypto.randomUUID();
5724
+ }
5725
+ return `places_${Date.now()}_${Math.random().toString(16).slice(2)}`;
5726
+ }
4773
5727
  function ProductCustomizer({
4774
5728
  product,
4775
5729
  onAddToCart,
@@ -4790,6 +5744,7 @@ function ProductCustomizer({
4790
5744
  const [bundleTotalPrice, setBundleTotalPrice] = useState(null);
4791
5745
  const [bundleReady, setBundleReady] = useState(false);
4792
5746
  const [selectedBillingPlan, setSelectedBillingPlan] = useState(null);
5747
+ const [customerInputValues, setCustomerInputValues] = useState({});
4793
5748
  const [specialInstructions, setSpecialInstructions] = useState("");
4794
5749
  const cart = useCart();
4795
5750
  const productType = product.type || "product";
@@ -4814,6 +5769,7 @@ function ProductCustomizer({
4814
5769
  setBundleTotalPrice(null);
4815
5770
  setBundleReady(false);
4816
5771
  setSelectedBillingPlan(null);
5772
+ setCustomerInputValues({});
4817
5773
  setSpecialInstructions("");
4818
5774
  }, [product.id, product.min_order_quantity]);
4819
5775
  const selectedAddOnOptions = useMemo(() => {
@@ -4876,7 +5832,15 @@ function ProductCustomizer({
4876
5832
  }),
4877
5833
  [product.id, quantity, selectedVariantId, normalizedAddOnOptionIds, isBundle, bundleSelections, isComposite, compositeSelections]
4878
5834
  );
4879
- const quoteEnabled = isComposite ? compositeReady : isBundle ? bundleReady : requiredAddOnsSatisfied;
5835
+ const requiredInputsSatisfied = useMemo(() => {
5836
+ if (!product.input_fields || product.input_fields.length === 0) return true;
5837
+ return product.input_fields.every((field) => {
5838
+ if (!field.is_required) return true;
5839
+ const val = customerInputValues[field.id];
5840
+ return val !== void 0 && val !== "" && val !== null;
5841
+ });
5842
+ }, [product.input_fields, customerInputValues]);
5843
+ const quoteEnabled = isComposite ? compositeReady && requiredInputsSatisfied : isBundle ? bundleReady && requiredInputsSatisfied : requiredAddOnsSatisfied && requiredInputsSatisfied;
4880
5844
  const { quote } = useQuote(quoteInput, {
4881
5845
  enabled: quoteEnabled
4882
5846
  });
@@ -4915,6 +5879,7 @@ function ProductCustomizer({
4915
5879
  compositeSelections: isComposite && compositeSelections.length > 0 ? compositeSelections : void 0,
4916
5880
  bundleSelections: isBundle && bundleSelections.length > 0 ? bundleSelections : void 0,
4917
5881
  billingPlanId: selectedBillingPlan?.id,
5882
+ customerInputs: Object.keys(customerInputValues).length > 0 ? Object.entries(customerInputValues).filter(([, v]) => v !== void 0 && v !== "").map(([fieldId, value]) => ({ field_id: fieldId, value })) : void 0,
4918
5883
  specialInstructions: specialInstructions.trim() || void 0
4919
5884
  };
4920
5885
  try {
@@ -4933,7 +5898,7 @@ function ProductCustomizer({
4933
5898
  } finally {
4934
5899
  setIsSubmitting(false);
4935
5900
  }
4936
- }, [product, quantity, selectedVariantId, selectedVariant, quoteId, normalizedAddOnOptionIds, selectedAddOnOptions, isComposite, compositeSelections, isBundle, bundleSelections, selectedBillingPlan, specialInstructions, isSubmitting, onAddToCart, cart]);
5901
+ }, [product, quantity, selectedVariantId, selectedVariant, quoteId, normalizedAddOnOptionIds, selectedAddOnOptions, isComposite, compositeSelections, isBundle, bundleSelections, selectedBillingPlan, customerInputValues, specialInstructions, isSubmitting, onAddToCart, cart]);
4937
5902
  return /* @__PURE__ */ jsxs("div", { "data-cimplify-customizer": true, className: cn("space-y-6", className, classNames?.root), children: [
4938
5903
  isComposite && product.groups && product.composite_id && /* @__PURE__ */ jsx(
4939
5904
  CompositeSelector,
@@ -4994,6 +5959,14 @@ function ProductCustomizer({
4994
5959
  currentQuantity: quantity
4995
5960
  }
4996
5961
  ),
5962
+ product.input_fields && product.input_fields.length > 0 && /* @__PURE__ */ jsx(
5963
+ CustomerInputFields,
5964
+ {
5965
+ fields: product.input_fields,
5966
+ values: customerInputValues,
5967
+ onChange: setCustomerInputValues
5968
+ }
5969
+ ),
4997
5970
  product.deposit_type && product.deposit_type !== "none" && product.deposit_amount && /* @__PURE__ */ jsx("div", { "data-cimplify-customizer-deposit": true, className: cn("text-sm text-muted-foreground", classNames?.depositInfo), children: product.deposit_type === "fixed" ? /* @__PURE__ */ jsxs("span", { children: [
4998
5971
  "Deposit required: ",
4999
5972
  /* @__PURE__ */ jsx(Price, { amount: product.deposit_amount })
@@ -5195,6 +6168,10 @@ function CartLineItemRow({
5195
6168
  ] }),
5196
6169
  (hasComposite || hasBundle) && /* @__PURE__ */ jsx("span", { "data-cimplify-cart-item-badge": true, children: hasComposite ? "Custom" : "Bundle" }),
5197
6170
  item.specialInstructions && /* @__PURE__ */ jsx("span", { "data-cimplify-cart-item-instructions": true, className: "text-xs text-muted-foreground", children: item.specialInstructions }),
6171
+ item.customerInputs && item.customerInputs.length > 0 && /* @__PURE__ */ jsx("div", { "data-cimplify-cart-customer-inputs": true, className: classNames?.customerInputs, children: item.customerInputs.map((input) => /* @__PURE__ */ jsxs("div", { "data-cimplify-cart-input": true, children: [
6172
+ /* @__PURE__ */ jsx("span", { "data-cimplify-cart-input-label": true, children: input.field_name }),
6173
+ /* @__PURE__ */ jsx("span", { "data-cimplify-cart-input-value": true, children: input.field_type === INPUT_FIELD_TYPE.File || input.field_type === INPUT_FIELD_TYPE.Image ? String(input.value).split("/").pop() || "Uploaded file" : String(input.value) })
6174
+ ] }, input.field_id)) }),
5198
6175
  (item.lineType === "service" || item.product.type === "service") && item.scheduledStart && /* @__PURE__ */ jsxs("div", { "data-cimplify-cart-service-info": true, className: classNames?.serviceInfo, children: [
5199
6176
  /* @__PURE__ */ jsxs("span", { children: [
5200
6177
  "\u{1F4C5}",
@@ -5882,6 +6859,98 @@ function DigitalProductCard({
5882
6859
  ] })
5883
6860
  ] });
5884
6861
  }
6862
+ function BundleProductCard({
6863
+ product,
6864
+ onQuickAdd,
6865
+ renderImage,
6866
+ renderLink,
6867
+ className
6868
+ }) {
6869
+ const image = product.image_url || product.images?.[0] || "";
6870
+ const components = product.components || [];
6871
+ const componentCount = components.length;
6872
+ const onSale = isOnSale(product);
6873
+ const discount = getDiscountPercentage(product);
6874
+ const hasVariantChoice = components.some((c) => c.allow_variant_choice);
6875
+ return /* @__PURE__ */ jsxs(CardShell, { product, renderLink, className, children: [
6876
+ /* @__PURE__ */ jsxs(CardImage, { src: image, alt: product.name, aspectRatio: "square", renderImage, children: [
6877
+ /* @__PURE__ */ jsx("span", { className: "absolute top-3 left-3 text-[11px] font-semibold tracking-wide bg-indigo-50 text-indigo-700 border border-indigo-200/60 px-2 py-0.5 rounded-md", children: "Bundle" }),
6878
+ componentCount > 0 && /* @__PURE__ */ jsxs("span", { className: "absolute top-3 right-3 text-[11px] font-semibold tracking-wide bg-background/90 backdrop-blur text-foreground px-2 py-0.5 rounded-md", children: [
6879
+ componentCount,
6880
+ " item",
6881
+ componentCount !== 1 ? "s" : ""
6882
+ ] }),
6883
+ /* @__PURE__ */ jsx(
6884
+ QuickAddButton,
6885
+ {
6886
+ onClick: () => onQuickAdd?.(product),
6887
+ className: "absolute bottom-3 right-3"
6888
+ }
6889
+ )
6890
+ ] }),
6891
+ /* @__PURE__ */ jsxs("div", { className: "p-3.5", children: [
6892
+ /* @__PURE__ */ jsx("h3", { className: "text-[14.5px] font-semibold text-foreground leading-snug tracking-tight truncate", children: product.name }),
6893
+ componentCount > 0 && /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-muted-foreground mt-1 truncate", children: [
6894
+ "Includes: ",
6895
+ components.map((c) => c.product_name || c.product_id).join(", ")
6896
+ ] }),
6897
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-2.5", children: [
6898
+ /* @__PURE__ */ jsx(Price, { amount: product.default_price, className: "text-sm font-bold" }),
6899
+ onSale && product.bundle_price && /* @__PURE__ */ jsxs(Fragment, { children: [
6900
+ /* @__PURE__ */ jsx(Price, { amount: getBasePrice(product), className: "text-xs text-muted-foreground line-through" }),
6901
+ discount != null && discount > 0 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-semibold text-emerald-600", children: [
6902
+ "Save ",
6903
+ discount,
6904
+ "%"
6905
+ ] })
6906
+ ] })
6907
+ ] }),
6908
+ hasVariantChoice && /* @__PURE__ */ jsx("span", { className: "inline-block mt-2 text-[10px] font-medium text-muted-foreground px-1.5 py-0.5 bg-muted rounded", children: "Customizable" })
6909
+ ] })
6910
+ ] });
6911
+ }
6912
+ function CompositeProductCard({
6913
+ product,
6914
+ onQuickAdd,
6915
+ renderImage,
6916
+ renderLink,
6917
+ className
6918
+ }) {
6919
+ const image = product.image_url || product.images?.[0] || "";
6920
+ const groups = product.groups || [];
6921
+ const groupCount = groups.length;
6922
+ groups.reduce((sum, g) => sum + g.components.length, 0);
6923
+ const tags = product.tags || [];
6924
+ return /* @__PURE__ */ jsxs(CardShell, { product, renderLink, className, children: [
6925
+ /* @__PURE__ */ jsxs(CardImage, { src: image, alt: product.name, aspectRatio: "square", renderImage, children: [
6926
+ /* @__PURE__ */ jsx("span", { className: "absolute top-3 left-3 text-[11px] font-semibold tracking-wide bg-violet-50 text-violet-700 border border-violet-200/60 px-2 py-0.5 rounded-md", children: "Build Your Own" }),
6927
+ groupCount > 0 && /* @__PURE__ */ jsxs("span", { className: "absolute top-3 right-3 text-[11px] font-semibold tracking-wide bg-background/90 backdrop-blur text-foreground px-2 py-0.5 rounded-md", children: [
6928
+ groupCount,
6929
+ " choice",
6930
+ groupCount !== 1 ? "s" : ""
6931
+ ] }),
6932
+ /* @__PURE__ */ jsx(
6933
+ QuickAddButton,
6934
+ {
6935
+ onClick: () => onQuickAdd?.(product),
6936
+ className: "absolute bottom-3 right-3"
6937
+ }
6938
+ )
6939
+ ] }),
6940
+ /* @__PURE__ */ jsxs("div", { className: "p-3.5", children: [
6941
+ /* @__PURE__ */ jsx("h3", { className: "text-[14.5px] font-semibold text-foreground leading-snug tracking-tight truncate", children: product.name }),
6942
+ product.description && /* @__PURE__ */ jsx("p", { className: "text-[12px] text-muted-foreground mt-1 line-clamp-1", children: product.description }),
6943
+ groupCount > 0 && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground mt-1 truncate", children: groups.map((g) => g.name).join(" + ") }),
6944
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mt-2.5", children: [
6945
+ /* @__PURE__ */ jsxs("span", { className: "text-sm font-bold", children: [
6946
+ "From ",
6947
+ /* @__PURE__ */ jsx(Price, { amount: product.default_price })
6948
+ ] }),
6949
+ tags.length > 0 && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium text-muted-foreground px-1.5 py-0.5 bg-muted rounded", children: tags[0] })
6950
+ ] })
6951
+ ] })
6952
+ ] });
6953
+ }
5885
6954
  function formatDuration2(minutes, unit) {
5886
6955
  if (unit && unit !== "minutes") return `${minutes} ${unit}`;
5887
6956
  if (minutes >= 60) {
@@ -6343,6 +7412,8 @@ var CardVariant = /* @__PURE__ */ ((CardVariant2) => {
6343
7412
  CardVariant2["Retail"] = "retail";
6344
7413
  CardVariant2["Wholesale"] = "wholesale";
6345
7414
  CardVariant2["Digital"] = "digital";
7415
+ CardVariant2["Bundle"] = "bundle";
7416
+ CardVariant2["Composite"] = "composite";
6346
7417
  CardVariant2["Standard"] = "standard";
6347
7418
  CardVariant2["Compact"] = "compact";
6348
7419
  CardVariant2["Schedule"] = "schedule";
@@ -6367,11 +7438,19 @@ var BUILT_IN_CARDS = {
6367
7438
  ["rental" /* Rental */]: RentalServiceCard,
6368
7439
  ["accommodation" /* Accommodation */]: AccommodationCard,
6369
7440
  ["lease" /* Lease */]: LeaseServiceCard,
7441
+ ["bundle" /* Bundle */]: BundleProductCard,
7442
+ ["composite" /* Composite */]: CompositeProductCard,
6370
7443
  ["subscription" /* Subscription */]: SubscriptionCard
6371
7444
  };
6372
7445
  var RENTAL_UNITS = /* @__PURE__ */ new Set([DURATION_UNIT.Days, DURATION_UNIT.Hours]);
6373
7446
  var LEASE_UNITS = /* @__PURE__ */ new Set([DURATION_UNIT.Weeks, DURATION_UNIT.Months, DURATION_UNIT.Years]);
6374
7447
  function resolveCardVariant(product) {
7448
+ if (product.type === PRODUCT_TYPE.Bundle) {
7449
+ return "bundle" /* Bundle */;
7450
+ }
7451
+ if (product.type === PRODUCT_TYPE.Composite) {
7452
+ return "composite" /* Composite */;
7453
+ }
6375
7454
  if (product.quantity_pricing && product.quantity_pricing.length > 1) {
6376
7455
  return "wholesale" /* Wholesale */;
6377
7456
  }
@@ -6561,13 +7640,13 @@ function ProductGrid({
6561
7640
  className,
6562
7641
  classNames
6563
7642
  }) {
6564
- const rawId = React3.useId();
7643
+ const rawId = React10.useId();
6565
7644
  const gridId = `cimplify-grid-${rawId.replace(/:/g, "")}`;
6566
7645
  const sm = columns?.sm ?? 1;
6567
7646
  const md = columns?.md ?? 2;
6568
7647
  const lg = columns?.lg ?? 3;
6569
7648
  const xl = columns?.xl ?? 4;
6570
- const css = React3.useMemo(
7649
+ const css = React10.useMemo(
6571
7650
  () => [
6572
7651
  `#${gridId}{display:grid;grid-template-columns:repeat(${sm},1fr);gap:1rem}`,
6573
7652
  `@media(min-width:768px){#${gridId}{grid-template-columns:repeat(${md},1fr)}}`,
@@ -6928,7 +8007,7 @@ function CategoryGrid({
6928
8007
  enabled: categoriesProp === void 0
6929
8008
  });
6930
8009
  const categories = categoriesProp ?? fetched;
6931
- const rawId = React3.useId();
8010
+ const rawId = React10.useId();
6932
8011
  const gridId = `cimplify-cat-grid-${rawId.replace(/:/g, "")}`;
6933
8012
  const sm = columns?.sm ?? 2;
6934
8013
  const md = columns?.md ?? 3;
@@ -7154,7 +8233,7 @@ function OrderSummary({
7154
8233
  /* @__PURE__ */ jsx("p", { children: order.delivery_address })
7155
8234
  ] }),
7156
8235
  /* @__PURE__ */ jsx("div", { "data-cimplify-order-items": true, className: classNames?.items, children: order.items.map(
7157
- (item) => renderLineItem ? /* @__PURE__ */ jsx(React3.Fragment, { children: renderLineItem(item) }, item.id) : /* @__PURE__ */ jsxs("div", { "data-cimplify-order-line-item": true, className: classNames?.lineItem, children: [
8236
+ (item) => renderLineItem ? /* @__PURE__ */ jsx(React10.Fragment, { children: renderLineItem(item) }, item.id) : /* @__PURE__ */ jsxs("div", { "data-cimplify-order-line-item": true, className: classNames?.lineItem, children: [
7158
8237
  /* @__PURE__ */ jsxs("div", { "data-cimplify-order-line-info": true, children: [
7159
8238
  /* @__PURE__ */ jsxs("span", { "data-cimplify-order-line-qty": true, children: [
7160
8239
  item.quantity,
@@ -8135,7 +9214,7 @@ function DefaultCatalogueLayout({
8135
9214
  /* @__PURE__ */ jsx("p", { className: "text-lg font-medium", children: "No products found" }),
8136
9215
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Try adjusting your filters" })
8137
9216
  ] }) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-4", children: products.map(
8138
- (product) => renderCard ? /* @__PURE__ */ jsx(React3.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
9217
+ (product) => renderCard ? /* @__PURE__ */ jsx(React10.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
8139
9218
  ProductCard,
8140
9219
  {
8141
9220
  product,
@@ -8242,7 +9321,7 @@ function CompactCatalogueLayout({
8242
9321
  /* @__PURE__ */ jsx("p", { className: "text-lg font-medium", children: "No items found" }),
8243
9322
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Try a different category or search" })
8244
9323
  ] }) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 lg:grid-cols-3 gap-5", children: products.map(
8245
- (product) => renderCard ? /* @__PURE__ */ jsx(React3.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
9324
+ (product) => renderCard ? /* @__PURE__ */ jsx(React10.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
8246
9325
  ProductCard,
8247
9326
  {
8248
9327
  product,
@@ -8265,8 +9344,8 @@ var BUILT_IN_LAYOUTS = {
8265
9344
  ["compact" /* Compact */]: CompactCatalogueLayout
8266
9345
  };
8267
9346
  function useDebounce(value, delay) {
8268
- const [debounced, setDebounced] = React3.useState(value);
8269
- React3.useEffect(() => {
9347
+ const [debounced, setDebounced] = React10.useState(value);
9348
+ React10.useEffect(() => {
8270
9349
  const timer = setTimeout(() => setDebounced(value), delay);
8271
9350
  return () => clearTimeout(timer);
8272
9351
  }, [value, delay]);
@@ -8994,12 +10073,161 @@ function DigitalProductLayout({
8994
10073
  )
8995
10074
  ] });
8996
10075
  }
10076
+ function BundleProductLayout({
10077
+ product,
10078
+ onAddToCart,
10079
+ renderBreadcrumb,
10080
+ relatedProducts,
10081
+ showRelated = true,
10082
+ className
10083
+ }) {
10084
+ const images = product.images || (product.image_url ? [product.image_url] : []);
10085
+ const components = product.components || [];
10086
+ const onSale = isOnSale(product);
10087
+ const discount = getDiscountPercentage(product);
10088
+ const baseTotal = components.reduce(
10089
+ (sum, c) => sum + parsePrice(c.effective_price ?? 0) * (c.quantity ?? 1),
10090
+ 0
10091
+ );
10092
+ const bundlePrice = parsePrice(product.default_price);
10093
+ const savings = baseTotal > bundlePrice ? baseTotal - bundlePrice : 0;
10094
+ return /* @__PURE__ */ jsxs("div", { "data-cimplify-product-layout": "bundle", className: cn("space-y-8", className), children: [
10095
+ renderBreadcrumb?.(product),
10096
+ /* @__PURE__ */ jsx(
10097
+ TwoColumnGrid,
10098
+ {
10099
+ left: /* @__PURE__ */ jsx(
10100
+ ProductImageGallery,
10101
+ {
10102
+ images,
10103
+ productName: product.name
10104
+ }
10105
+ ),
10106
+ right: /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
10107
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
10108
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-indigo-700 bg-indigo-50 border border-indigo-200/60 px-2.5 py-1 rounded-md", children: "Bundle" }),
10109
+ savings > 0 && /* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold text-emerald-700 bg-emerald-50 border border-emerald-200/60 px-2.5 py-1 rounded-md", children: [
10110
+ "Save ",
10111
+ discount,
10112
+ "%"
10113
+ ] }),
10114
+ /* @__PURE__ */ jsx(TagPills, { tags: product.tags || [] })
10115
+ ] }),
10116
+ /* @__PURE__ */ jsxs("div", { children: [
10117
+ /* @__PURE__ */ jsx("h1", { "data-cimplify-product-layout-name": true, className: "text-3xl lg:text-4xl font-bold tracking-tight", children: product.name }),
10118
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-3 mt-2", children: [
10119
+ /* @__PURE__ */ jsx(Price, { amount: product.default_price, className: "text-2xl font-bold" }),
10120
+ onSale && baseTotal > 0 && /* @__PURE__ */ jsx(Price, { amount: baseTotal, className: "text-lg text-muted-foreground line-through" })
10121
+ ] })
10122
+ ] }),
10123
+ product.description && /* @__PURE__ */ jsx("p", { "data-cimplify-product-layout-description": true, className: "text-muted-foreground leading-relaxed", children: product.description }),
10124
+ components.length > 0 && /* @__PURE__ */ jsxs("div", { "data-cimplify-bundle-components": true, className: "space-y-3", children: [
10125
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Included in this bundle" }),
10126
+ /* @__PURE__ */ jsx("div", { className: "divide-y divide-border border border-border rounded-lg overflow-hidden", children: components.map((component) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 p-3", children: [
10127
+ component.product_image_url && /* @__PURE__ */ jsx("div", { className: "w-12 h-12 rounded-lg overflow-hidden bg-muted shrink-0", children: /* @__PURE__ */ jsx("img", { src: component.product_image_url, alt: component.product_name, className: "w-full h-full object-cover" }) }),
10128
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
10129
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: component.product_name }),
10130
+ component.quantity > 1 && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
10131
+ "Qty: ",
10132
+ component.quantity
10133
+ ] }),
10134
+ component.allow_variant_choice && /* @__PURE__ */ jsx("p", { className: "text-xs text-violet-600 font-medium", children: "Choose variant" })
10135
+ ] }),
10136
+ /* @__PURE__ */ jsx(Price, { amount: component.effective_price ?? 0, className: "text-sm text-muted-foreground shrink-0" })
10137
+ ] }, component.id)) })
10138
+ ] }),
10139
+ savings > 0 && /* @__PURE__ */ jsxs("div", { "data-cimplify-bundle-savings": true, className: "flex items-center gap-2 p-3 bg-emerald-50 border border-emerald-200/60 text-sm text-emerald-800", children: [
10140
+ /* @__PURE__ */ jsx("svg", { className: "w-4 h-4 shrink-0", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }),
10141
+ "You save ",
10142
+ /* @__PURE__ */ jsx(Price, { amount: savings, className: "font-semibold" }),
10143
+ " with this bundle"
10144
+ ] }),
10145
+ /* @__PURE__ */ jsx(CustomAttributesTable, { attributes: product.custom_attributes || [] }),
10146
+ /* @__PURE__ */ jsx(
10147
+ ProductCustomizer,
10148
+ {
10149
+ product,
10150
+ onAddToCart
10151
+ }
10152
+ )
10153
+ ] })
10154
+ }
10155
+ ),
10156
+ showRelated && relatedProducts && relatedProducts.length > 0 && /* @__PURE__ */ jsx(RelatedProductsSection, { products: relatedProducts })
10157
+ ] });
10158
+ }
10159
+ function CompositeProductLayout({
10160
+ product,
10161
+ onAddToCart,
10162
+ renderBreadcrumb,
10163
+ relatedProducts,
10164
+ showRelated = true,
10165
+ className
10166
+ }) {
10167
+ const images = product.images || (product.image_url ? [product.image_url] : []);
10168
+ const groups = product.groups || [];
10169
+ return /* @__PURE__ */ jsxs("div", { "data-cimplify-product-layout": "composite", className: cn("space-y-8", className), children: [
10170
+ renderBreadcrumb?.(product),
10171
+ /* @__PURE__ */ jsx(
10172
+ TwoColumnGrid,
10173
+ {
10174
+ left: /* @__PURE__ */ jsx(
10175
+ ProductImageGallery,
10176
+ {
10177
+ images,
10178
+ productName: product.name
10179
+ }
10180
+ ),
10181
+ right: /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
10182
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
10183
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-violet-700 bg-violet-50 border border-violet-200/60 px-2.5 py-1 rounded-md", children: "Build Your Own" }),
10184
+ groups.length > 0 && /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-muted-foreground bg-muted px-2.5 py-1 rounded-md", children: [
10185
+ groups.length,
10186
+ " choice",
10187
+ groups.length !== 1 ? "s" : ""
10188
+ ] }),
10189
+ /* @__PURE__ */ jsx(TagPills, { tags: product.tags || [] })
10190
+ ] }),
10191
+ /* @__PURE__ */ jsxs("div", { children: [
10192
+ /* @__PURE__ */ jsx("h1", { "data-cimplify-product-layout-name": true, className: "text-3xl lg:text-4xl font-bold tracking-tight", children: product.name }),
10193
+ /* @__PURE__ */ jsxs("p", { className: "text-lg text-muted-foreground mt-2", children: [
10194
+ "From ",
10195
+ /* @__PURE__ */ jsx(Price, { amount: product.default_price, className: "font-semibold text-foreground" })
10196
+ ] })
10197
+ ] }),
10198
+ product.description && /* @__PURE__ */ jsx("p", { "data-cimplify-product-layout-description": true, className: "text-muted-foreground leading-relaxed", children: product.description }),
10199
+ groups.length > 0 && /* @__PURE__ */ jsx("div", { "data-cimplify-composite-overview": true, className: "flex flex-wrap gap-2", children: groups.map((group) => {
10200
+ const availableCount = group.components.filter((c) => c.is_available && !c.is_archived).length;
10201
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 bg-muted rounded-lg text-sm", children: [
10202
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: group.name }),
10203
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-xs", children: [
10204
+ group.min_selections > 0 ? `Pick ${group.min_selections}` : "Optional",
10205
+ ` \xB7 ${availableCount} option${availableCount !== 1 ? "s" : ""}`
10206
+ ] })
10207
+ ] }, group.id);
10208
+ }) }),
10209
+ /* @__PURE__ */ jsx(CustomAttributesTable, { attributes: product.custom_attributes || [] }),
10210
+ /* @__PURE__ */ jsx(
10211
+ ProductCustomizer,
10212
+ {
10213
+ product,
10214
+ onAddToCart
10215
+ }
10216
+ )
10217
+ ] })
10218
+ }
10219
+ ),
10220
+ showRelated && relatedProducts && relatedProducts.length > 0 && /* @__PURE__ */ jsx(RelatedProductsSection, { products: relatedProducts })
10221
+ ] });
10222
+ }
8997
10223
  var ProductTemplate = /* @__PURE__ */ ((ProductTemplate2) => {
8998
10224
  ProductTemplate2["Default"] = "default";
8999
10225
  ProductTemplate2["Food"] = "food";
9000
10226
  ProductTemplate2["Wholesale"] = "wholesale";
9001
10227
  ProductTemplate2["Service"] = "service";
9002
10228
  ProductTemplate2["Digital"] = "digital";
10229
+ ProductTemplate2["Bundle"] = "bundle";
10230
+ ProductTemplate2["Composite"] = "composite";
9003
10231
  ProductTemplate2["Physical"] = "physical";
9004
10232
  return ProductTemplate2;
9005
10233
  })(ProductTemplate || {});
@@ -9008,6 +10236,8 @@ var BUILT_IN_LAYOUTS2 = {
9008
10236
  ["wholesale" /* Wholesale */]: WholesaleProductLayout,
9009
10237
  ["service" /* Service */]: ServiceProductLayout,
9010
10238
  ["digital" /* Digital */]: DigitalProductLayout,
10239
+ ["bundle" /* Bundle */]: BundleProductLayout,
10240
+ ["composite" /* Composite */]: CompositeProductLayout,
9011
10241
  ["default" /* Default */]: DefaultProductLayout
9012
10242
  };
9013
10243
  function resolveTemplateKey(product) {
@@ -9015,6 +10245,12 @@ function resolveTemplateKey(product) {
9015
10245
  if (typeof metaTemplate === "string" && metaTemplate.trim()) {
9016
10246
  return metaTemplate.trim();
9017
10247
  }
10248
+ if (product.type === PRODUCT_TYPE.Bundle) {
10249
+ return "bundle" /* Bundle */;
10250
+ }
10251
+ if (product.type === PRODUCT_TYPE.Composite) {
10252
+ return "composite" /* Composite */;
10253
+ }
9018
10254
  if (product.quantity_pricing && product.quantity_pricing.length > 1) {
9019
10255
  return "wholesale" /* Wholesale */;
9020
10256
  }
@@ -9379,7 +10615,7 @@ function DefaultCollectionLayout({
9379
10615
  ] })
9380
10616
  ] }),
9381
10617
  filtered.length > 0 ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4", children: filtered.map(
9382
- (product) => renderCard ? /* @__PURE__ */ jsx(React3.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
10618
+ (product) => renderCard ? /* @__PURE__ */ jsx(React10.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
9383
10619
  ProductCard,
9384
10620
  {
9385
10621
  product,
@@ -9432,7 +10668,7 @@ function FeaturedCollectionLayout({
9432
10668
  ] })
9433
10669
  ] }),
9434
10670
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: products.map(
9435
- (product) => renderCard ? /* @__PURE__ */ jsx(React3.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
10671
+ (product) => renderCard ? /* @__PURE__ */ jsx(React10.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
9436
10672
  ProductCard,
9437
10673
  {
9438
10674
  product,
@@ -9556,7 +10792,7 @@ function CatalogueCollectionLayout({
9556
10792
  ] })
9557
10793
  ] }),
9558
10794
  filtered.length > 0 ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4", children: filtered.map(
9559
- (product) => renderCard ? /* @__PURE__ */ jsx(React3.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
10795
+ (product) => renderCard ? /* @__PURE__ */ jsx(React10.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
9560
10796
  ProductCard,
9561
10797
  {
9562
10798
  product,
@@ -9936,7 +11172,7 @@ function DefaultSearchLayout({
9936
11172
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Try a different search term" }),
9937
11173
  /* @__PURE__ */ jsx("button", { onClick: clear, className: "text-sm font-medium text-primary mt-4 hover:underline", children: "Clear search" })
9938
11174
  ] }) : results.length > 0 ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-4", children: results.map(
9939
- (product) => renderCard ? /* @__PURE__ */ jsx(React3.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
11175
+ (product) => renderCard ? /* @__PURE__ */ jsx(React10.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
9940
11176
  ProductCard,
9941
11177
  {
9942
11178
  product,
@@ -10038,7 +11274,7 @@ function CompactSearchLayout({
10038
11274
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Try a different search term" }),
10039
11275
  /* @__PURE__ */ jsx("button", { onClick: clear, className: "text-sm font-medium text-primary mt-4 hover:underline", children: "Clear search" })
10040
11276
  ] }) : results.length > 0 ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 lg:grid-cols-3 gap-5", children: results.map(
10041
- (product) => renderCard ? /* @__PURE__ */ jsx(React3.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
11277
+ (product) => renderCard ? /* @__PURE__ */ jsx(React10.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
10042
11278
  ProductCard,
10043
11279
  {
10044
11280
  product,
@@ -10603,4 +11839,4 @@ function ResourcePicker({
10603
11839
  );
10604
11840
  }
10605
11841
 
10606
- export { AccommodationCard, Ad, AdProvider, AddOnSelector, AddressElement, AuthElement, AvailabilityBadge, BillingPlanSelector, BookingCard, BookingList, BundleSelector, CardImage, CardShell, CardVariant, CartPage, CartSummary, CartTemplate, CatalogueCollectionLayout, CataloguePage, CatalogueTemplate, CategoryFilter, CategoryGrid, CheckoutPage, CimplifyAccount, CimplifyCheckout, CimplifyProvider, CollectionPage, CollectionTemplate, CompactCartLayout, CompactCatalogueLayout, CompactSearchLayout, CompactServiceCard, CompositeSelector, CurrencySelector, CustomAttributesTable, DateSlotPicker, DealBanner, DealsPage, DefaultCartLayout, DefaultCatalogueLayout, DefaultCollectionLayout, DefaultProductLayout, DefaultSearchLayout, DeliveryEstimate, DigitalProductCard, DigitalProductLayout, DiscountInput, ElementsProvider, FeaturedCollectionLayout, FoodProductCard, FoodProductLayout, InventoryBadge, LeaseServiceCard, LocationPicker, MetadataStringList, OrderDetailPage, OrderHistory, OrderHistoryPage, OrderSummary, PaymentElement, Price, PriceRange, ProductCard, ProductCustomizer, ProductGrid, ProductImageGallery, ProductPage, ProductSheet, ProductTemplate, PropertiesTable, QuantitySelector, QuickAddButton, RecentlyViewed, RecommendationCarousel, RelatedProductsSection, RentalServiceCard, ResourcePicker, RetailProductCard, SaleBadge, ScheduleServiceCard, SearchInput, SearchPage, SearchTemplate, ServiceProductLayout, SessionMessageBanner, SlotPicker, SoldOutOverlay, StaffPicker, StandardServiceCard, StoreNav, SubscriptionCard, TagPills, TwoColumnGrid, VariantSelector, VolumePricing, WholesaleProductCard, WholesaleProductLayout, WishlistButton, cn, roomToResource, useActivityState, useAds, useAttributeDefinitions, useAvailableSlots, useBillingPlans, useBookings, useBootstrap, useBundles, useCart, useCategories, useCheckout, useCimplify, useCimplifyClient, useCollection, useCollections, useDeals, useDeliveryFee, useElements, useElementsReady, useFxRate, useLocations, useOptionalCimplify, useOrder, useOrders, useProduct, useProductAvailability, useProductDeals, useProductPrice, useProductSchedules, useProducts, useProductsOnSale, usePropertyFacets, useQuote, useRecommendations, useSearch, useServiceAvailability, useServices, useTaxonomies, useTaxonomy, useTaxonomyPath, useValidateDiscount, useVariantSelector };
11842
+ export { AccommodationCard, Ad, AdProvider, AddOnSelector, AddressElement, AuthElement, AvailabilityBadge, BillingPlanSelector, BookingCard, BookingList, BundleProductCard, BundleProductLayout, BundleSelector, CardImage, CardShell, CardVariant, CartPage, CartSummary, CartTemplate, CatalogueCollectionLayout, CataloguePage, CatalogueTemplate, CategoryFilter, CategoryGrid, CheckoutPage, CimplifyAccount, CimplifyCheckout, CimplifyProvider, CollectionPage, CollectionTemplate, CompactCartLayout, CompactCatalogueLayout, CompactSearchLayout, CompactServiceCard, CompositeProductCard, CompositeProductLayout, CompositeSelector, CurrencySelector, CustomAttributesTable, CustomerInputFields, DateSlotPicker, DealBanner, DealsPage, DefaultCartLayout, DefaultCatalogueLayout, DefaultCollectionLayout, DefaultProductLayout, DefaultSearchLayout, DeliveryEstimate, DigitalProductCard, DigitalProductLayout, DiscountInput, ElementsProvider, FeaturedCollectionLayout, FoodProductCard, FoodProductLayout, InventoryBadge, LeaseServiceCard, LocationPicker, MetadataStringList, OrderDetailPage, OrderHistory, OrderHistoryPage, OrderSummary, PaymentElement, Price, PriceRange, ProductCard, ProductCustomizer, ProductGrid, ProductImageGallery, ProductPage, ProductSheet, ProductTemplate, PropertiesTable, QuantitySelector, QuickAddButton, RecentlyViewed, RecommendationCarousel, RelatedProductsSection, RentalServiceCard, ResourcePicker, RetailProductCard, SaleBadge, ScheduleServiceCard, SearchInput, SearchPage, SearchTemplate, ServiceProductLayout, SessionMessageBanner, SlotPicker, SoldOutOverlay, StaffPicker, StandardServiceCard, StoreNav, SubscriptionCard, TagPills, TwoColumnGrid, VariantSelector, VolumePricing, WholesaleProductCard, WholesaleProductLayout, WishlistButton, cn, roomToResource, useActivityState, useAds, useAttributeDefinitions, useAvailableSlots, useBillingPlans, useBookings, useBootstrap, useBundles, useCart, useCategories, useCheckout, useCimplify, useCimplifyClient, useCollection, useCollections, useDeals, useDeliveryFee, useElements, useElementsReady, useFxRate, useLocations, useOptionalCimplify, useOrder, useOrders, useProduct, useProductAvailability, useProductDeals, useProductPrice, useProductSchedules, useProducts, useProductsOnSale, usePropertyFacets, useQuote, useRecommendations, useSearch, useServiceAvailability, useServices, useSubscription, useSubscriptions, useTaxonomies, useTaxonomy, useTaxonomyPath, useValidateDiscount, useVariantSelector };