@cimplify/sdk 0.28.0 → 0.30.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-FFUCCUG4.mjs';
3
- export { getVariantDisplayName } from './chunk-FFUCCUG4.mjs';
2
+ import { DURATION_UNIT, getVariantDisplayName, INPUT_FIELD_TYPE, PRODUCT_TYPE, createCimplifyClient, RENDER_HINT } from './chunk-WGAVNFEQ.mjs';
3
+ export { getVariantDisplayName } from './chunk-WGAVNFEQ.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.";
@@ -3308,6 +3310,122 @@ function useBootstrap(options = {}) {
3308
3310
  const { data, isLoading, error, refetch } = bootstrapQuery(options);
3309
3311
  return { bootstrap: data, isLoading, error, refetch };
3310
3312
  }
3313
+ var POLL_INTERVAL_MS = 3e3;
3314
+ var POLL_IDLE_INTERVAL_MS = 15e3;
3315
+ var IDLE_TIMEOUT_MS = 6e4;
3316
+ function useChat(options = {}) {
3317
+ const context = useOptionalCimplifyClient();
3318
+ const client = options.client ?? context?.client;
3319
+ const [messages, setMessages] = useState([]);
3320
+ const [isLoading, setIsLoading] = useState(false);
3321
+ const [isSending, setIsSending] = useState(false);
3322
+ const [error, setError] = useState(null);
3323
+ const [conversationOpened, setConversationOpened] = useState(false);
3324
+ const pollRef = useRef(null);
3325
+ const lastActivityRef = useRef(Date.now());
3326
+ const openedRef = useRef(false);
3327
+ const messagesRef = useRef([]);
3328
+ messagesRef.current = messages;
3329
+ const pollInterval = options.pollInterval ?? POLL_INTERVAL_MS;
3330
+ const pollIdleInterval = options.pollIdleInterval ?? POLL_IDLE_INTERVAL_MS;
3331
+ const pollMessages = useCallback(async () => {
3332
+ if (!client) return;
3333
+ const isIdle = Date.now() - lastActivityRef.current > IDLE_TIMEOUT_MS;
3334
+ if (isIdle) return;
3335
+ const last = messagesRef.current[messagesRef.current.length - 1];
3336
+ const result = await client.support.getMessages({
3337
+ after: last?.created_at,
3338
+ limit: 50
3339
+ });
3340
+ if (result.ok && result.value.length > 0) {
3341
+ setMessages((prev) => {
3342
+ const ids = new Set(prev.map((m) => m.id));
3343
+ const fresh = result.value.filter((m) => !ids.has(m.id));
3344
+ return fresh.length > 0 ? [...prev, ...fresh] : prev;
3345
+ });
3346
+ }
3347
+ }, [client]);
3348
+ const startPolling = useCallback(() => {
3349
+ if (pollRef.current) clearInterval(pollRef.current);
3350
+ const interval = Date.now() - lastActivityRef.current > IDLE_TIMEOUT_MS ? pollIdleInterval : pollInterval;
3351
+ pollRef.current = setInterval(pollMessages, interval);
3352
+ }, [pollMessages, pollInterval, pollIdleInterval]);
3353
+ const stopPolling = useCallback(() => {
3354
+ if (pollRef.current) {
3355
+ clearInterval(pollRef.current);
3356
+ pollRef.current = null;
3357
+ }
3358
+ }, []);
3359
+ useEffect(() => stopPolling, [stopPolling]);
3360
+ const openConversation = useCallback(async () => {
3361
+ if (openedRef.current || !client) return;
3362
+ openedRef.current = true;
3363
+ setIsLoading(true);
3364
+ setError(null);
3365
+ const result = await client.support.openConversation();
3366
+ if (result.ok) {
3367
+ setMessages(result.value.messages);
3368
+ setConversationOpened(true);
3369
+ startPolling();
3370
+ } else {
3371
+ openedRef.current = false;
3372
+ setError(result.error);
3373
+ }
3374
+ setIsLoading(false);
3375
+ }, [client, startPolling]);
3376
+ const send = useCallback(
3377
+ async (content) => {
3378
+ if (!client || !content.trim()) return;
3379
+ setError(null);
3380
+ lastActivityRef.current = Date.now();
3381
+ if (!openedRef.current) {
3382
+ await openConversation();
3383
+ if (!openedRef.current) return;
3384
+ }
3385
+ const optimisticId = `opt_${Date.now()}`;
3386
+ const optimistic = {
3387
+ id: optimisticId,
3388
+ sender_type: "customer",
3389
+ content: content.trim(),
3390
+ content_type: "text",
3391
+ attachments: [],
3392
+ metadata: {},
3393
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
3394
+ };
3395
+ setMessages((prev) => [...prev, optimistic]);
3396
+ setIsSending(true);
3397
+ const result = await client.support.sendMessage(content.trim());
3398
+ if (result.ok) {
3399
+ setMessages(
3400
+ (prev) => prev.map((m) => m.id === optimisticId ? result.value : m)
3401
+ );
3402
+ } else {
3403
+ setMessages((prev) => prev.filter((m) => m.id !== optimisticId));
3404
+ setError(result.error);
3405
+ }
3406
+ setIsSending(false);
3407
+ },
3408
+ [client, openConversation]
3409
+ );
3410
+ const startConversation = useCallback(
3411
+ async (starterText) => {
3412
+ await openConversation();
3413
+ if (starterText) {
3414
+ await send(starterText);
3415
+ }
3416
+ },
3417
+ [openConversation, send]
3418
+ );
3419
+ return {
3420
+ messages,
3421
+ isLoading,
3422
+ isSending,
3423
+ error,
3424
+ isActive: conversationOpened && messages.length > 0,
3425
+ send,
3426
+ startConversation
3427
+ };
3428
+ }
3311
3429
  var ElementsContext = createContext({
3312
3430
  elements: null,
3313
3431
  isReady: false
@@ -4659,8 +4777,26 @@ function CompositeSelector({
4659
4777
  )
4660
4778
  ] });
4661
4779
  }
4780
+
4781
+ // src/react/utils/format-duration.ts
4782
+ var UNIT_LABELS = {
4783
+ minutes: { singular: "min", plural: "min" },
4784
+ hours: { singular: "hr", plural: "hrs" },
4785
+ days: { singular: "day", plural: "days" },
4786
+ nights: { singular: "night", plural: "nights" },
4787
+ weeks: { singular: "week", plural: "weeks" },
4788
+ months: { singular: "month", plural: "months" },
4789
+ years: { singular: "year", plural: "years" }
4790
+ };
4791
+ function formatDuration2(value, unit) {
4792
+ const resolved = unit ?? "minutes";
4793
+ const labels = UNIT_LABELS[resolved];
4794
+ const label = value === 1 ? labels.singular : labels.plural;
4795
+ return `${value} ${label}`;
4796
+ }
4662
4797
  var FREQUENCY_LABELS = {
4663
4798
  weekly: "Weekly",
4799
+ biweekly: "Biweekly",
4664
4800
  monthly: "Monthly",
4665
4801
  quarterly: "Quarterly",
4666
4802
  annually: "Annually"
@@ -4848,6 +4984,880 @@ function VolumePricing({
4848
4984
  }
4849
4985
  );
4850
4986
  }
4987
+ function CustomerInputFields({
4988
+ fields,
4989
+ values,
4990
+ onChange,
4991
+ onFileUpload,
4992
+ className,
4993
+ classNames
4994
+ }) {
4995
+ const clientContext = useOptionalCimplifyClient();
4996
+ if (fields.length === 0) return null;
4997
+ const sorted = [...fields].sort((a, b) => a.display_order - b.display_order);
4998
+ const setValue = useCallback(
4999
+ (fieldId, value) => {
5000
+ onChange({ ...values, [fieldId]: value });
5001
+ },
5002
+ [values, onChange]
5003
+ );
5004
+ const defaultFileUpload = useCallback(
5005
+ async (file) => {
5006
+ if (onFileUpload) return onFileUpload(file, {});
5007
+ if (!clientContext?.client) throw new Error("No upload provider available");
5008
+ const result = await clientContext.client.uploads.upload(file);
5009
+ if (!result.ok) throw new Error(result.error.message);
5010
+ return result.value.url;
5011
+ },
5012
+ [onFileUpload, clientContext]
5013
+ );
5014
+ return /* @__PURE__ */ jsx("div", { "data-cimplify-customer-inputs": true, className: cn("space-y-4", className, classNames?.root), children: sorted.map((field) => /* @__PURE__ */ jsx(
5015
+ InputField,
5016
+ {
5017
+ field,
5018
+ value: values[field.id],
5019
+ onValueChange: (value) => setValue(field.id, value),
5020
+ onFileUpload: onFileUpload ?? (clientContext?.client ? (_file, _field) => defaultFileUpload(_file) : void 0),
5021
+ classNames
5022
+ },
5023
+ field.id
5024
+ )) });
5025
+ }
5026
+ function InputField({
5027
+ field,
5028
+ value,
5029
+ onValueChange,
5030
+ onFileUpload,
5031
+ classNames
5032
+ }) {
5033
+ const hasPriceAdjustment = field.price_adjustment != null && parsePrice(field.price_adjustment) > 0;
5034
+ return /* @__PURE__ */ jsxs("div", { "data-cimplify-customer-input": true, "data-field-type": field.field_type, className: classNames?.field, children: [
5035
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1.5", children: [
5036
+ /* @__PURE__ */ jsx("label", { className: cn("text-sm font-semibold", classNames?.label), children: field.name }),
5037
+ field.is_required && /* @__PURE__ */ jsx("span", { className: cn("text-xs text-destructive", classNames?.required), children: "Required" }),
5038
+ hasPriceAdjustment && /* @__PURE__ */ jsxs("span", { className: cn("text-xs text-muted-foreground", classNames?.priceAdjustment), children: [
5039
+ "+",
5040
+ /* @__PURE__ */ jsx(Price, { amount: field.price_adjustment })
5041
+ ] })
5042
+ ] }),
5043
+ field.help_text && /* @__PURE__ */ jsx("p", { className: cn("text-xs text-muted-foreground mb-1.5", classNames?.helpText), children: field.help_text }),
5044
+ /* @__PURE__ */ jsx(
5045
+ FieldInput,
5046
+ {
5047
+ field,
5048
+ value,
5049
+ onValueChange,
5050
+ onFileUpload,
5051
+ classNames
5052
+ }
5053
+ )
5054
+ ] });
5055
+ }
5056
+ function FieldInput({
5057
+ field,
5058
+ value,
5059
+ onValueChange,
5060
+ onFileUpload,
5061
+ classNames
5062
+ }) {
5063
+ const inputClass = cn(
5064
+ "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",
5065
+ classNames?.input
5066
+ );
5067
+ switch (field.field_type) {
5068
+ case INPUT_FIELD_TYPE.Text:
5069
+ case INPUT_FIELD_TYPE.Url:
5070
+ return /* @__PURE__ */ jsx(
5071
+ "input",
5072
+ {
5073
+ type: field.field_type === INPUT_FIELD_TYPE.Url ? "url" : "text",
5074
+ value: typeof value === "string" ? value : "",
5075
+ onChange: (e) => onValueChange(e.target.value),
5076
+ placeholder: field.placeholder,
5077
+ required: field.is_required,
5078
+ maxLength: field.validation?.max_length ?? void 0,
5079
+ className: inputClass
5080
+ }
5081
+ );
5082
+ case INPUT_FIELD_TYPE.Textarea:
5083
+ return /* @__PURE__ */ jsx(
5084
+ "textarea",
5085
+ {
5086
+ value: typeof value === "string" ? value : "",
5087
+ onChange: (e) => onValueChange(e.target.value),
5088
+ placeholder: field.placeholder,
5089
+ required: field.is_required,
5090
+ maxLength: field.validation?.max_length ?? void 0,
5091
+ rows: 3,
5092
+ className: cn(inputClass, "resize-none", classNames?.textarea)
5093
+ }
5094
+ );
5095
+ case INPUT_FIELD_TYPE.Number:
5096
+ return /* @__PURE__ */ jsx(
5097
+ "input",
5098
+ {
5099
+ type: "number",
5100
+ value: typeof value === "number" ? value : "",
5101
+ onChange: (e) => onValueChange(e.target.value ? parseFloat(e.target.value) : void 0),
5102
+ placeholder: field.placeholder,
5103
+ required: field.is_required,
5104
+ min: field.validation?.min_value ?? void 0,
5105
+ max: field.validation?.max_value ?? void 0,
5106
+ className: inputClass
5107
+ }
5108
+ );
5109
+ case INPUT_FIELD_TYPE.Select:
5110
+ return /* @__PURE__ */ jsxs(
5111
+ "select",
5112
+ {
5113
+ value: typeof value === "string" ? value : "",
5114
+ onChange: (e) => onValueChange(e.target.value || void 0),
5115
+ required: field.is_required,
5116
+ className: cn(inputClass, classNames?.select),
5117
+ children: [
5118
+ /* @__PURE__ */ jsx("option", { value: "", children: field.placeholder || "Select..." }),
5119
+ (field.options || []).map((opt) => /* @__PURE__ */ jsx("option", { value: opt, children: opt }, opt))
5120
+ ]
5121
+ }
5122
+ );
5123
+ case INPUT_FIELD_TYPE.Radio:
5124
+ return /* @__PURE__ */ jsx("div", { className: cn("space-y-2", classNames?.radioGroup), children: (field.options || []).map((opt) => /* @__PURE__ */ jsxs(
5125
+ "label",
5126
+ {
5127
+ className: cn(
5128
+ "flex items-center gap-2 text-sm cursor-pointer",
5129
+ classNames?.radioOption
5130
+ ),
5131
+ children: [
5132
+ /* @__PURE__ */ jsx(
5133
+ "input",
5134
+ {
5135
+ type: "radio",
5136
+ name: field.id,
5137
+ value: opt,
5138
+ checked: value === opt,
5139
+ onChange: () => onValueChange(opt),
5140
+ className: "accent-primary"
5141
+ }
5142
+ ),
5143
+ opt
5144
+ ]
5145
+ },
5146
+ opt
5147
+ )) });
5148
+ case INPUT_FIELD_TYPE.Checkbox:
5149
+ return /* @__PURE__ */ jsxs("label", { className: cn("flex items-center gap-2 text-sm cursor-pointer", classNames?.checkboxLabel), children: [
5150
+ /* @__PURE__ */ jsx(
5151
+ "input",
5152
+ {
5153
+ type: "checkbox",
5154
+ checked: value === true,
5155
+ onChange: (e) => onValueChange(e.target.checked),
5156
+ className: "accent-primary w-4 h-4"
5157
+ }
5158
+ ),
5159
+ field.placeholder || field.name
5160
+ ] });
5161
+ case INPUT_FIELD_TYPE.Color:
5162
+ return /* @__PURE__ */ jsx(
5163
+ "input",
5164
+ {
5165
+ type: "color",
5166
+ value: typeof value === "string" ? value : "#000000",
5167
+ onChange: (e) => onValueChange(e.target.value),
5168
+ className: cn("w-12 h-10 rounded-md border border-input cursor-pointer", classNames?.colorInput)
5169
+ }
5170
+ );
5171
+ case INPUT_FIELD_TYPE.Date:
5172
+ return /* @__PURE__ */ jsx(
5173
+ "input",
5174
+ {
5175
+ type: "date",
5176
+ value: typeof value === "string" ? value : "",
5177
+ onChange: (e) => onValueChange(e.target.value || void 0),
5178
+ required: field.is_required,
5179
+ className: inputClass
5180
+ }
5181
+ );
5182
+ case INPUT_FIELD_TYPE.File:
5183
+ case INPUT_FIELD_TYPE.Image:
5184
+ return /* @__PURE__ */ jsx(
5185
+ FileUploadInput,
5186
+ {
5187
+ field,
5188
+ value,
5189
+ onValueChange,
5190
+ onFileUpload,
5191
+ classNames
5192
+ }
5193
+ );
5194
+ case INPUT_FIELD_TYPE.Email:
5195
+ return /* @__PURE__ */ jsx(
5196
+ "input",
5197
+ {
5198
+ type: "email",
5199
+ value: typeof value === "string" ? value : "",
5200
+ onChange: (e) => onValueChange(e.target.value),
5201
+ placeholder: field.placeholder || "email@example.com",
5202
+ required: field.is_required,
5203
+ className: inputClass
5204
+ }
5205
+ );
5206
+ case INPUT_FIELD_TYPE.DateTime: {
5207
+ const dtLocalValue = typeof value === "string" && value.includes("T") ? value.slice(0, 16) : typeof value === "string" ? value : "";
5208
+ return /* @__PURE__ */ jsx(
5209
+ "input",
5210
+ {
5211
+ type: "datetime-local",
5212
+ value: dtLocalValue,
5213
+ onChange: (e) => {
5214
+ if (!e.target.value) {
5215
+ onValueChange(void 0);
5216
+ return;
5217
+ }
5218
+ const date = new Date(e.target.value);
5219
+ onValueChange(Number.isNaN(date.getTime()) ? e.target.value : date.toISOString());
5220
+ },
5221
+ required: field.is_required,
5222
+ className: inputClass
5223
+ }
5224
+ );
5225
+ }
5226
+ case INPUT_FIELD_TYPE.Time:
5227
+ return /* @__PURE__ */ jsx(
5228
+ "input",
5229
+ {
5230
+ type: "time",
5231
+ value: typeof value === "string" ? value : "",
5232
+ onChange: (e) => onValueChange(e.target.value || void 0),
5233
+ required: field.is_required,
5234
+ className: inputClass
5235
+ }
5236
+ );
5237
+ case INPUT_FIELD_TYPE.MultiSelect:
5238
+ return /* @__PURE__ */ jsx(
5239
+ MultiSelectInput,
5240
+ {
5241
+ field,
5242
+ value,
5243
+ onValueChange,
5244
+ classNames
5245
+ }
5246
+ );
5247
+ case INPUT_FIELD_TYPE.DateRange:
5248
+ return /* @__PURE__ */ jsx(
5249
+ DateRangeInput,
5250
+ {
5251
+ field,
5252
+ value,
5253
+ onValueChange,
5254
+ classNames,
5255
+ inputClass
5256
+ }
5257
+ );
5258
+ case INPUT_FIELD_TYPE.Address:
5259
+ return /* @__PURE__ */ jsx(
5260
+ AddressInput,
5261
+ {
5262
+ field,
5263
+ value,
5264
+ onValueChange,
5265
+ classNames,
5266
+ inputClass
5267
+ }
5268
+ );
5269
+ case INPUT_FIELD_TYPE.Location:
5270
+ return /* @__PURE__ */ jsx(
5271
+ LocationInput,
5272
+ {
5273
+ field,
5274
+ value,
5275
+ onValueChange,
5276
+ classNames,
5277
+ inputClass
5278
+ }
5279
+ );
5280
+ case INPUT_FIELD_TYPE.Phone:
5281
+ return /* @__PURE__ */ jsx(
5282
+ PhoneInput,
5283
+ {
5284
+ field,
5285
+ value,
5286
+ onValueChange,
5287
+ classNames,
5288
+ inputClass
5289
+ }
5290
+ );
5291
+ case INPUT_FIELD_TYPE.Signature:
5292
+ return /* @__PURE__ */ jsx(
5293
+ SignatureInput,
5294
+ {
5295
+ field,
5296
+ value,
5297
+ onValueChange,
5298
+ classNames
5299
+ }
5300
+ );
5301
+ default:
5302
+ return /* @__PURE__ */ jsx(
5303
+ "input",
5304
+ {
5305
+ type: "text",
5306
+ value: typeof value === "string" ? value : "",
5307
+ onChange: (e) => onValueChange(e.target.value),
5308
+ placeholder: field.placeholder,
5309
+ className: inputClass
5310
+ }
5311
+ );
5312
+ }
5313
+ }
5314
+ function FileUploadInput({
5315
+ field,
5316
+ value,
5317
+ onValueChange,
5318
+ onFileUpload,
5319
+ classNames
5320
+ }) {
5321
+ const [isUploading, setIsUploading] = React10.useState(false);
5322
+ const fileUrl = typeof value === "string" ? value : void 0;
5323
+ const acceptedFormats = field.validation?.accepted_formats;
5324
+ const accept = acceptedFormats ? acceptedFormats.map((f) => `.${f}`).join(",") : field.field_type === INPUT_FIELD_TYPE.Image ? "image/*" : void 0;
5325
+ const handleFile = async (file) => {
5326
+ if (!onFileUpload) return;
5327
+ if (field.validation?.max_size_mb) {
5328
+ const maxBytes = field.validation.max_size_mb * 1024 * 1024;
5329
+ if (file.size > maxBytes) {
5330
+ return;
5331
+ }
5332
+ }
5333
+ setIsUploading(true);
5334
+ try {
5335
+ const url = await onFileUpload(file, field);
5336
+ onValueChange(url);
5337
+ } finally {
5338
+ setIsUploading(false);
5339
+ }
5340
+ };
5341
+ return /* @__PURE__ */ jsx("div", { className: cn("space-y-2", classNames?.fileInput), children: fileUrl ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
5342
+ 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" }),
5343
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-foreground truncate", children: fileUrl.split("/").pop() }) }),
5344
+ /* @__PURE__ */ jsx(
5345
+ "button",
5346
+ {
5347
+ type: "button",
5348
+ onClick: () => onValueChange(void 0),
5349
+ className: "text-xs text-muted-foreground hover:text-destructive transition-colors",
5350
+ children: "Remove"
5351
+ }
5352
+ )
5353
+ ] }) : /* @__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: [
5354
+ /* @__PURE__ */ jsx(
5355
+ "input",
5356
+ {
5357
+ type: "file",
5358
+ accept,
5359
+ onChange: (e) => {
5360
+ const file = e.target.files?.[0];
5361
+ if (file) void handleFile(file);
5362
+ },
5363
+ className: "hidden",
5364
+ disabled: isUploading
5365
+ }
5366
+ ),
5367
+ isUploading ? /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "Uploading..." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
5368
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: field.placeholder || `Upload ${field.field_type === INPUT_FIELD_TYPE.Image ? "image" : "file"}` }),
5369
+ acceptedFormats && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground/60 mt-1", children: [
5370
+ acceptedFormats.map((f) => f.toUpperCase()).join(", "),
5371
+ field.validation?.max_size_mb && ` \xB7 Max ${field.validation.max_size_mb}MB`
5372
+ ] })
5373
+ ] })
5374
+ ] }) });
5375
+ }
5376
+ function MultiSelectInput({
5377
+ field,
5378
+ value,
5379
+ onValueChange,
5380
+ classNames
5381
+ }) {
5382
+ const selected = Array.isArray(value) ? value : [];
5383
+ const options = field.options || [];
5384
+ const maxSelections = field.validation?.max_selections;
5385
+ const toggle = (opt) => {
5386
+ const next = selected.includes(opt) ? selected.filter((s) => s !== opt) : maxSelections && selected.length >= maxSelections ? selected : [...selected, opt];
5387
+ onValueChange(next.length > 0 ? next : void 0);
5388
+ };
5389
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", classNames?.multiSelectGroup), children: [
5390
+ options.map((opt) => /* @__PURE__ */ jsxs(
5391
+ "label",
5392
+ {
5393
+ className: cn(
5394
+ "flex items-center gap-2 text-sm cursor-pointer",
5395
+ classNames?.multiSelectOption
5396
+ ),
5397
+ children: [
5398
+ /* @__PURE__ */ jsx(
5399
+ "input",
5400
+ {
5401
+ type: "checkbox",
5402
+ checked: selected.includes(opt),
5403
+ onChange: () => toggle(opt),
5404
+ className: "accent-primary w-4 h-4"
5405
+ }
5406
+ ),
5407
+ opt
5408
+ ]
5409
+ },
5410
+ opt
5411
+ )),
5412
+ maxSelections && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
5413
+ selected.length,
5414
+ "/",
5415
+ maxSelections,
5416
+ " selected"
5417
+ ] })
5418
+ ] });
5419
+ }
5420
+ function DateRangeInput({
5421
+ field,
5422
+ value,
5423
+ onValueChange,
5424
+ classNames,
5425
+ inputClass
5426
+ }) {
5427
+ const range = value && typeof value === "object" ? value : {};
5428
+ const update = (key, v) => {
5429
+ const next = { ...range, [key]: v };
5430
+ onValueChange(next.start || next.end ? next : void 0);
5431
+ };
5432
+ return /* @__PURE__ */ jsxs("div", { className: cn("grid grid-cols-2 gap-3", classNames?.dateRangeInput), children: [
5433
+ /* @__PURE__ */ jsxs("div", { children: [
5434
+ /* @__PURE__ */ jsx("label", { className: "text-xs text-muted-foreground mb-1 block", children: "Start" }),
5435
+ /* @__PURE__ */ jsx(
5436
+ "input",
5437
+ {
5438
+ type: "date",
5439
+ value: range.start || "",
5440
+ onChange: (e) => update("start", e.target.value),
5441
+ required: field.is_required,
5442
+ className: inputClass
5443
+ }
5444
+ )
5445
+ ] }),
5446
+ /* @__PURE__ */ jsxs("div", { children: [
5447
+ /* @__PURE__ */ jsx("label", { className: "text-xs text-muted-foreground mb-1 block", children: "End" }),
5448
+ /* @__PURE__ */ jsx(
5449
+ "input",
5450
+ {
5451
+ type: "date",
5452
+ value: range.end || "",
5453
+ onChange: (e) => update("end", e.target.value),
5454
+ required: field.is_required,
5455
+ className: inputClass
5456
+ }
5457
+ )
5458
+ ] })
5459
+ ] });
5460
+ }
5461
+ function AddressInput({
5462
+ field,
5463
+ value,
5464
+ onValueChange,
5465
+ classNames,
5466
+ inputClass
5467
+ }) {
5468
+ const clientContext = useOptionalCimplifyClient();
5469
+ const addr = value && typeof value === "object" ? value : null;
5470
+ const [inputText, setInputText] = React10.useState(addr?.formatted_address ?? "");
5471
+ const [suggestions, setSuggestions] = React10.useState([]);
5472
+ const [isOpen, setIsOpen] = React10.useState(false);
5473
+ const [highlightedIndex, setHighlightedIndex] = React10.useState(-1);
5474
+ const debounceRef = React10.useRef(null);
5475
+ const seqRef = React10.useRef(0);
5476
+ const sessionTokenRef = React10.useRef(createSessionToken());
5477
+ const blurTimeoutRef = React10.useRef(null);
5478
+ React10.useEffect(() => {
5479
+ return () => {
5480
+ if (debounceRef.current) clearTimeout(debounceRef.current);
5481
+ if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
5482
+ seqRef.current += 1;
5483
+ };
5484
+ }, []);
5485
+ const fetchSuggestions = React10.useCallback(async (query) => {
5486
+ const client = clientContext?.client;
5487
+ if (!client || query.length < 3) {
5488
+ setSuggestions([]);
5489
+ setIsOpen(false);
5490
+ return;
5491
+ }
5492
+ const seq = ++seqRef.current;
5493
+ const result = await client.places.autocomplete(query, sessionTokenRef.current);
5494
+ if (seqRef.current !== seq) return;
5495
+ if (result.ok && result.value.predictions.length > 0) {
5496
+ setSuggestions(result.value.predictions);
5497
+ setHighlightedIndex(0);
5498
+ setIsOpen(true);
5499
+ } else {
5500
+ setSuggestions([]);
5501
+ setIsOpen(false);
5502
+ }
5503
+ }, [clientContext]);
5504
+ const handleInputChange = (nextValue) => {
5505
+ setInputText(nextValue);
5506
+ if (debounceRef.current) clearTimeout(debounceRef.current);
5507
+ debounceRef.current = setTimeout(() => void fetchSuggestions(nextValue.trim()), 300);
5508
+ };
5509
+ const selectSuggestion = React10.useCallback(async (suggestion) => {
5510
+ if (debounceRef.current) clearTimeout(debounceRef.current);
5511
+ if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
5512
+ const fallback = suggestion.description.split(",")[0]?.trim() || suggestion.description;
5513
+ setInputText(fallback);
5514
+ setSuggestions([]);
5515
+ setIsOpen(false);
5516
+ const client = clientContext?.client;
5517
+ if (!client) return;
5518
+ const result = await client.places.details(suggestion.place_id, sessionTokenRef.current);
5519
+ sessionTokenRef.current = createSessionToken();
5520
+ if (result.ok) {
5521
+ const d = result.value;
5522
+ const addressValue = {
5523
+ formatted_address: d.formatted_address || fallback,
5524
+ street_address: d.street_address,
5525
+ apartment: d.apartment,
5526
+ city: d.city,
5527
+ region: d.region,
5528
+ postal_code: d.postal_code,
5529
+ country: d.country,
5530
+ latitude: d.latitude,
5531
+ longitude: d.longitude,
5532
+ place_id: d.place_id
5533
+ };
5534
+ setInputText(addressValue.formatted_address);
5535
+ onValueChange(addressValue);
5536
+ }
5537
+ }, [clientContext, onValueChange]);
5538
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2 relative", classNames?.addressInput), 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 an address...",
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
+ addr && /* @__PURE__ */ jsx(
5592
+ "input",
5593
+ {
5594
+ type: "text",
5595
+ value: addr.apartment || "",
5596
+ onChange: (e) => onValueChange({ ...addr, apartment: e.target.value || void 0 }),
5597
+ placeholder: "Apt, suite, unit (optional)",
5598
+ className: inputClass
5599
+ }
5600
+ )
5601
+ ] });
5602
+ }
5603
+ function LocationInput({
5604
+ field,
5605
+ value,
5606
+ onValueChange,
5607
+ classNames,
5608
+ inputClass
5609
+ }) {
5610
+ const clientContext = useOptionalCimplifyClient();
5611
+ const loc = value && typeof value === "object" ? value : null;
5612
+ const [inputText, setInputText] = React10.useState(loc?.label ?? "");
5613
+ const [suggestions, setSuggestions] = React10.useState([]);
5614
+ const [isOpen, setIsOpen] = React10.useState(false);
5615
+ const [highlightedIndex, setHighlightedIndex] = React10.useState(-1);
5616
+ const debounceRef = React10.useRef(null);
5617
+ const seqRef = React10.useRef(0);
5618
+ const sessionTokenRef = React10.useRef(createSessionToken());
5619
+ const blurTimeoutRef = React10.useRef(null);
5620
+ React10.useEffect(() => {
5621
+ return () => {
5622
+ if (debounceRef.current) clearTimeout(debounceRef.current);
5623
+ if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
5624
+ seqRef.current += 1;
5625
+ };
5626
+ }, []);
5627
+ const fetchSuggestions = React10.useCallback(async (query) => {
5628
+ const client = clientContext?.client;
5629
+ if (!client || query.length < 3) {
5630
+ setSuggestions([]);
5631
+ setIsOpen(false);
5632
+ return;
5633
+ }
5634
+ const seq = ++seqRef.current;
5635
+ const result = await client.places.autocomplete(query, sessionTokenRef.current);
5636
+ if (seqRef.current !== seq) return;
5637
+ if (result.ok && result.value.predictions.length > 0) {
5638
+ setSuggestions(result.value.predictions);
5639
+ setHighlightedIndex(0);
5640
+ setIsOpen(true);
5641
+ } else {
5642
+ setSuggestions([]);
5643
+ setIsOpen(false);
5644
+ }
5645
+ }, [clientContext]);
5646
+ const handleInputChange = (nextValue) => {
5647
+ setInputText(nextValue);
5648
+ if (debounceRef.current) clearTimeout(debounceRef.current);
5649
+ debounceRef.current = setTimeout(() => void fetchSuggestions(nextValue.trim()), 300);
5650
+ };
5651
+ const selectSuggestion = React10.useCallback(async (suggestion) => {
5652
+ if (debounceRef.current) clearTimeout(debounceRef.current);
5653
+ if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
5654
+ const label = suggestion.description.split(",")[0]?.trim() || suggestion.description;
5655
+ setInputText(label);
5656
+ setSuggestions([]);
5657
+ setIsOpen(false);
5658
+ const client = clientContext?.client;
5659
+ if (!client) return;
5660
+ const result = await client.places.details(suggestion.place_id, sessionTokenRef.current);
5661
+ sessionTokenRef.current = createSessionToken();
5662
+ if (result.ok) {
5663
+ const d = result.value;
5664
+ onValueChange({
5665
+ latitude: d.latitude,
5666
+ longitude: d.longitude,
5667
+ label: d.formatted_address || label
5668
+ });
5669
+ setInputText(d.formatted_address || label);
5670
+ }
5671
+ }, [clientContext, onValueChange]);
5672
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative", classNames?.locationInput), children: [
5673
+ /* @__PURE__ */ jsx(
5674
+ "input",
5675
+ {
5676
+ type: "text",
5677
+ value: inputText,
5678
+ onChange: (e) => handleInputChange(e.target.value),
5679
+ onFocus: () => {
5680
+ if (suggestions.length > 0) setIsOpen(true);
5681
+ },
5682
+ onBlur: () => {
5683
+ blurTimeoutRef.current = setTimeout(() => setIsOpen(false), 120);
5684
+ },
5685
+ onKeyDown: (e) => {
5686
+ if (e.key === "Escape") {
5687
+ setIsOpen(false);
5688
+ return;
5689
+ }
5690
+ if (!isOpen || suggestions.length === 0) return;
5691
+ if (e.key === "ArrowDown") {
5692
+ e.preventDefault();
5693
+ setHighlightedIndex((i) => i < suggestions.length - 1 ? i + 1 : 0);
5694
+ } else if (e.key === "ArrowUp") {
5695
+ e.preventDefault();
5696
+ setHighlightedIndex((i) => i > 0 ? i - 1 : suggestions.length - 1);
5697
+ } else if (e.key === "Enter" && highlightedIndex >= 0) {
5698
+ e.preventDefault();
5699
+ void selectSuggestion(suggestions[highlightedIndex]);
5700
+ }
5701
+ },
5702
+ placeholder: field.placeholder || "Search for a location...",
5703
+ required: field.is_required,
5704
+ autoComplete: "off",
5705
+ "aria-autocomplete": "list",
5706
+ "aria-expanded": isOpen && suggestions.length > 0,
5707
+ className: inputClass
5708
+ }
5709
+ ),
5710
+ 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(
5711
+ "button",
5712
+ {
5713
+ type: "button",
5714
+ role: "option",
5715
+ "aria-selected": i === highlightedIndex,
5716
+ onMouseDown: (e) => {
5717
+ e.preventDefault();
5718
+ void selectSuggestion(s);
5719
+ },
5720
+ 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"),
5721
+ children: s.description
5722
+ },
5723
+ s.place_id
5724
+ )) }),
5725
+ loc && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-1", children: [
5726
+ loc.latitude.toFixed(4),
5727
+ ", ",
5728
+ loc.longitude.toFixed(4)
5729
+ ] })
5730
+ ] });
5731
+ }
5732
+ function PhoneInput({
5733
+ field,
5734
+ value,
5735
+ onValueChange,
5736
+ classNames,
5737
+ inputClass
5738
+ }) {
5739
+ const phone = value && typeof value === "object" ? value : null;
5740
+ const [code, setCode] = React10.useState(phone?.country_code ?? "+1");
5741
+ const [number, setNumber] = React10.useState(phone?.number ?? "");
5742
+ const update = (nextCode, nextNumber) => {
5743
+ setCode(nextCode);
5744
+ setNumber(nextNumber);
5745
+ if (nextNumber) {
5746
+ onValueChange({
5747
+ country_code: nextCode,
5748
+ number: nextNumber,
5749
+ formatted: `${nextCode} ${nextNumber}`
5750
+ });
5751
+ } else {
5752
+ onValueChange(void 0);
5753
+ }
5754
+ };
5755
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex gap-2", classNames?.phoneInput), children: [
5756
+ /* @__PURE__ */ jsx(
5757
+ "input",
5758
+ {
5759
+ type: "text",
5760
+ value: code,
5761
+ onChange: (e) => update(e.target.value, number),
5762
+ placeholder: "+1",
5763
+ className: cn(inputClass, "w-20 shrink-0")
5764
+ }
5765
+ ),
5766
+ /* @__PURE__ */ jsx(
5767
+ "input",
5768
+ {
5769
+ type: "tel",
5770
+ value: number,
5771
+ onChange: (e) => update(code, e.target.value),
5772
+ placeholder: field.placeholder || "Phone number",
5773
+ required: field.is_required,
5774
+ className: inputClass
5775
+ }
5776
+ )
5777
+ ] });
5778
+ }
5779
+ function SignatureInput({
5780
+ field,
5781
+ value,
5782
+ onValueChange,
5783
+ classNames
5784
+ }) {
5785
+ const sig = value && typeof value === "object" ? value : null;
5786
+ const canvasRef = React10.useRef(null);
5787
+ const isDrawing = React10.useRef(false);
5788
+ const getCtx = () => canvasRef.current?.getContext("2d") ?? null;
5789
+ const startDraw = (e) => {
5790
+ const ctx = getCtx();
5791
+ if (!ctx) return;
5792
+ isDrawing.current = true;
5793
+ const rect = canvasRef.current.getBoundingClientRect();
5794
+ ctx.beginPath();
5795
+ ctx.moveTo(e.clientX - rect.left, e.clientY - rect.top);
5796
+ canvasRef.current.setPointerCapture(e.pointerId);
5797
+ };
5798
+ const draw = (e) => {
5799
+ if (!isDrawing.current) return;
5800
+ const ctx = getCtx();
5801
+ if (!ctx) return;
5802
+ const rect = canvasRef.current.getBoundingClientRect();
5803
+ ctx.lineWidth = 2;
5804
+ ctx.lineCap = "round";
5805
+ const style = getComputedStyle(canvasRef.current);
5806
+ ctx.strokeStyle = style.color || "#000";
5807
+ ctx.lineTo(e.clientX - rect.left, e.clientY - rect.top);
5808
+ ctx.stroke();
5809
+ };
5810
+ const endDraw = () => {
5811
+ if (!isDrawing.current) return;
5812
+ isDrawing.current = false;
5813
+ const canvas = canvasRef.current;
5814
+ if (!canvas) return;
5815
+ onValueChange({
5816
+ data_url: canvas.toDataURL("image/png"),
5817
+ signer_name: sig?.signer_name
5818
+ });
5819
+ };
5820
+ const clear = () => {
5821
+ const canvas = canvasRef.current;
5822
+ if (!canvas) return;
5823
+ const ctx = canvas.getContext("2d");
5824
+ if (ctx) ctx.clearRect(0, 0, canvas.width, canvas.height);
5825
+ onValueChange(void 0);
5826
+ };
5827
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", classNames?.signatureCanvas), children: [
5828
+ /* @__PURE__ */ jsxs("div", { className: "relative rounded-md border border-input overflow-hidden", children: [
5829
+ /* @__PURE__ */ jsx(
5830
+ "canvas",
5831
+ {
5832
+ ref: canvasRef,
5833
+ width: 400,
5834
+ height: 150,
5835
+ className: "w-full touch-none cursor-crosshair bg-background text-foreground",
5836
+ onPointerDown: startDraw,
5837
+ onPointerMove: draw,
5838
+ onPointerUp: endDraw,
5839
+ onPointerLeave: endDraw
5840
+ }
5841
+ ),
5842
+ sig?.data_url && /* @__PURE__ */ jsx(
5843
+ "button",
5844
+ {
5845
+ type: "button",
5846
+ onClick: clear,
5847
+ className: "absolute top-1.5 right-1.5 text-xs text-muted-foreground hover:text-destructive transition-colors",
5848
+ children: "Clear"
5849
+ }
5850
+ )
5851
+ ] }),
5852
+ !sig?.data_url && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: field.placeholder || "Draw your signature above" })
5853
+ ] });
5854
+ }
5855
+ function createSessionToken() {
5856
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
5857
+ return crypto.randomUUID();
5858
+ }
5859
+ return `places_${Date.now()}_${Math.random().toString(16).slice(2)}`;
5860
+ }
4851
5861
  function ProductCustomizer({
4852
5862
  product,
4853
5863
  onAddToCart,
@@ -4868,6 +5878,7 @@ function ProductCustomizer({
4868
5878
  const [bundleTotalPrice, setBundleTotalPrice] = useState(null);
4869
5879
  const [bundleReady, setBundleReady] = useState(false);
4870
5880
  const [selectedBillingPlan, setSelectedBillingPlan] = useState(null);
5881
+ const [customerInputValues, setCustomerInputValues] = useState({});
4871
5882
  const [specialInstructions, setSpecialInstructions] = useState("");
4872
5883
  const cart = useCart();
4873
5884
  const productType = product.type || "product";
@@ -4892,6 +5903,7 @@ function ProductCustomizer({
4892
5903
  setBundleTotalPrice(null);
4893
5904
  setBundleReady(false);
4894
5905
  setSelectedBillingPlan(null);
5906
+ setCustomerInputValues({});
4895
5907
  setSpecialInstructions("");
4896
5908
  }, [product.id, product.min_order_quantity]);
4897
5909
  const selectedAddOnOptions = useMemo(() => {
@@ -4954,7 +5966,15 @@ function ProductCustomizer({
4954
5966
  }),
4955
5967
  [product.id, quantity, selectedVariantId, normalizedAddOnOptionIds, isBundle, bundleSelections, isComposite, compositeSelections]
4956
5968
  );
4957
- const quoteEnabled = isComposite ? compositeReady : isBundle ? bundleReady : requiredAddOnsSatisfied;
5969
+ const requiredInputsSatisfied = useMemo(() => {
5970
+ if (!product.input_fields || product.input_fields.length === 0) return true;
5971
+ return product.input_fields.every((field) => {
5972
+ if (!field.is_required) return true;
5973
+ const val = customerInputValues[field.id];
5974
+ return val !== void 0 && val !== "" && val !== null;
5975
+ });
5976
+ }, [product.input_fields, customerInputValues]);
5977
+ const quoteEnabled = isComposite ? compositeReady && requiredInputsSatisfied : isBundle ? bundleReady && requiredInputsSatisfied : requiredAddOnsSatisfied && requiredInputsSatisfied;
4958
5978
  const { quote } = useQuote(quoteInput, {
4959
5979
  enabled: quoteEnabled
4960
5980
  });
@@ -4993,6 +6013,7 @@ function ProductCustomizer({
4993
6013
  compositeSelections: isComposite && compositeSelections.length > 0 ? compositeSelections : void 0,
4994
6014
  bundleSelections: isBundle && bundleSelections.length > 0 ? bundleSelections : void 0,
4995
6015
  billingPlanId: selectedBillingPlan?.id,
6016
+ customerInputs: Object.keys(customerInputValues).length > 0 ? Object.entries(customerInputValues).filter(([, v]) => v !== void 0 && v !== "").map(([fieldId, value]) => ({ field_id: fieldId, value })) : void 0,
4996
6017
  specialInstructions: specialInstructions.trim() || void 0
4997
6018
  };
4998
6019
  try {
@@ -5011,7 +6032,7 @@ function ProductCustomizer({
5011
6032
  } finally {
5012
6033
  setIsSubmitting(false);
5013
6034
  }
5014
- }, [product, quantity, selectedVariantId, selectedVariant, quoteId, normalizedAddOnOptionIds, selectedAddOnOptions, isComposite, compositeSelections, isBundle, bundleSelections, selectedBillingPlan, specialInstructions, isSubmitting, onAddToCart, cart]);
6035
+ }, [product, quantity, selectedVariantId, selectedVariant, quoteId, normalizedAddOnOptionIds, selectedAddOnOptions, isComposite, compositeSelections, isBundle, bundleSelections, selectedBillingPlan, customerInputValues, specialInstructions, isSubmitting, onAddToCart, cart]);
5015
6036
  return /* @__PURE__ */ jsxs("div", { "data-cimplify-customizer": true, className: cn("space-y-6", className, classNames?.root), children: [
5016
6037
  isComposite && product.groups && product.composite_id && /* @__PURE__ */ jsx(
5017
6038
  CompositeSelector,
@@ -5072,6 +6093,14 @@ function ProductCustomizer({
5072
6093
  currentQuantity: quantity
5073
6094
  }
5074
6095
  ),
6096
+ product.input_fields && product.input_fields.length > 0 && /* @__PURE__ */ jsx(
6097
+ CustomerInputFields,
6098
+ {
6099
+ fields: product.input_fields,
6100
+ values: customerInputValues,
6101
+ onChange: setCustomerInputValues
6102
+ }
6103
+ ),
5075
6104
  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: [
5076
6105
  "Deposit required: ",
5077
6106
  /* @__PURE__ */ jsx(Price, { amount: product.deposit_amount })
@@ -5091,9 +6120,7 @@ function ProductCustomizer({
5091
6120
  )) }),
5092
6121
  isService && product.duration_minutes != null && /* @__PURE__ */ jsxs("div", { "data-cimplify-customizer-duration": true, className: cn("text-sm text-muted-foreground", classNames?.duration), children: [
5093
6122
  "Duration: ",
5094
- product.duration_minutes,
5095
- " ",
5096
- product.duration_unit ?? "minutes"
6123
+ formatDuration2(product.duration_minutes, product.duration_unit)
5097
6124
  ] }),
5098
6125
  showSpecialInstructions && !isDigital && /* @__PURE__ */ jsx("div", { "data-cimplify-customizer-special-instructions": true, children: /* @__PURE__ */ jsx(
5099
6126
  "textarea",
@@ -5273,6 +6300,10 @@ function CartLineItemRow({
5273
6300
  ] }),
5274
6301
  (hasComposite || hasBundle) && /* @__PURE__ */ jsx("span", { "data-cimplify-cart-item-badge": true, children: hasComposite ? "Custom" : "Bundle" }),
5275
6302
  item.specialInstructions && /* @__PURE__ */ jsx("span", { "data-cimplify-cart-item-instructions": true, className: "text-xs text-muted-foreground", children: item.specialInstructions }),
6303
+ 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: [
6304
+ /* @__PURE__ */ jsx("span", { "data-cimplify-cart-input-label": true, children: input.field_name }),
6305
+ /* @__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) })
6306
+ ] }, input.field_id)) }),
5276
6307
  (item.lineType === "service" || item.product.type === "service") && item.scheduledStart && /* @__PURE__ */ jsxs("div", { "data-cimplify-cart-service-info": true, className: classNames?.serviceInfo, children: [
5277
6308
  /* @__PURE__ */ jsxs("span", { children: [
5278
6309
  "\u{1F4C5}",
@@ -6052,7 +7083,7 @@ function CompositeProductCard({
6052
7083
  ] })
6053
7084
  ] });
6054
7085
  }
6055
- function formatDuration2(minutes, unit) {
7086
+ function formatDuration3(minutes, unit) {
6056
7087
  if (unit && unit !== "minutes") return `${minutes} ${unit}`;
6057
7088
  if (minutes >= 60) {
6058
7089
  const h = Math.floor(minutes / 60);
@@ -6073,7 +7104,7 @@ function StandardServiceCard({
6073
7104
  return /* @__PURE__ */ jsxs(CardShell, { product, renderLink, className, children: [
6074
7105
  /* @__PURE__ */ jsxs(CardImage, { src: image, alt: product.name, aspectRatio: "16/9", renderImage, children: [
6075
7106
  /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-black/50 to-transparent pointer-events-none" }),
6076
- product.duration_minutes != null && /* @__PURE__ */ jsx("span", { className: "absolute bottom-3 left-3 inline-flex items-center gap-1 text-[12px] font-semibold px-2.5 py-1 rounded-lg bg-background/92 backdrop-blur-xl text-foreground shadow-sm", children: formatDuration2(product.duration_minutes, product.duration_unit) }),
7107
+ product.duration_minutes != null && /* @__PURE__ */ jsx("span", { className: "absolute bottom-3 left-3 inline-flex items-center gap-1 text-[12px] font-semibold px-2.5 py-1 rounded-lg bg-background/92 backdrop-blur-xl text-foreground shadow-sm", children: formatDuration3(product.duration_minutes, product.duration_unit) }),
6077
7108
  /* @__PURE__ */ jsx("span", { className: "absolute bottom-3 right-3 text-white font-bold text-lg drop-shadow-sm", children: /* @__PURE__ */ jsx(Price, { amount: product.default_price }) })
6078
7109
  ] }),
6079
7110
  /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
@@ -6107,7 +7138,7 @@ function StandardServiceCard({
6107
7138
  ] })
6108
7139
  ] });
6109
7140
  }
6110
- function formatDuration3(minutes, unit) {
7141
+ function formatDuration4(minutes, unit) {
6111
7142
  if (unit && unit !== "minutes") return `${minutes} ${unit}`;
6112
7143
  if (minutes >= 60) {
6113
7144
  const h = Math.floor(minutes / 60);
@@ -6132,7 +7163,7 @@ function CompactServiceCard({
6132
7163
  /* @__PURE__ */ jsx("h3", { className: "text-[14px] font-semibold text-foreground leading-tight truncate", children: product.name }),
6133
7164
  product.description && /* @__PURE__ */ jsx("p", { className: "text-[12px] text-muted-foreground mt-0.5 truncate", children: product.description }),
6134
7165
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-2 flex-wrap", children: [
6135
- product.duration_minutes != null && /* @__PURE__ */ jsx("span", { className: "text-[10.5px] font-medium text-muted-foreground px-1.5 py-0.5 bg-muted rounded", children: formatDuration3(product.duration_minutes, product.duration_unit) }),
7166
+ product.duration_minutes != null && /* @__PURE__ */ jsx("span", { className: "text-[10.5px] font-medium text-muted-foreground px-1.5 py-0.5 bg-muted rounded", children: formatDuration4(product.duration_minutes, product.duration_unit) }),
6136
7167
  /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-[10.5px] font-medium text-emerald-600", children: [
6137
7168
  /* @__PURE__ */ jsx("span", { className: "w-[6px] h-[6px] rounded-full bg-emerald-500" }),
6138
7169
  "Available"
@@ -6172,7 +7203,7 @@ function ScheduleServiceCard({
6172
7203
  }) {
6173
7204
  const image = product.image_url || product.images?.[0] || "";
6174
7205
  const href = `/products/${product.slug}`;
6175
- const durationLabel = product.duration_minutes != null ? `${product.duration_minutes} ${product.duration_unit ?? "min"}` : null;
7206
+ const durationLabel = product.duration_minutes != null ? formatDuration2(product.duration_minutes, product.duration_unit) : null;
6176
7207
  const displaySlots = (slots || []).slice(0, 3);
6177
7208
  const shellClass = cn(
6178
7209
  "group block text-left cursor-pointer bg-card rounded-[14px] overflow-hidden",
@@ -6225,7 +7256,7 @@ function ScheduleServiceCard({
6225
7256
  }
6226
7257
  return /* @__PURE__ */ jsx("a", { href, className: shellClass, "data-cimplify-card": true, children: inner });
6227
7258
  }
6228
- var UNIT_LABELS = {
7259
+ var UNIT_LABELS2 = {
6229
7260
  minutes: "min",
6230
7261
  hours: "hr",
6231
7262
  days: "day",
@@ -6243,7 +7274,7 @@ function RentalServiceCard({
6243
7274
  }) {
6244
7275
  const image = product.image_url || product.images?.[0] || "";
6245
7276
  const hasDeposit = product.deposit_type && product.deposit_type !== "none" && product.deposit_amount;
6246
- const unit = product.duration_unit ? UNIT_LABELS[product.duration_unit] || product.duration_unit : "day";
7277
+ const unit = product.duration_unit ? UNIT_LABELS2[product.duration_unit] || product.duration_unit : "day";
6247
7278
  const status = product.inventory_status;
6248
7279
  return /* @__PURE__ */ jsxs(CardShell, { product, renderLink, className, children: [
6249
7280
  /* @__PURE__ */ jsxs(CardImage, { src: image, alt: product.name, aspectRatio: "16/9", renderImage, children: [
@@ -6363,7 +7394,7 @@ function AccommodationCard({
6363
7394
  ] })
6364
7395
  ] });
6365
7396
  }
6366
- var UNIT_LABELS2 = {
7397
+ var UNIT_LABELS3 = {
6367
7398
  weeks: "week",
6368
7399
  months: "mo",
6369
7400
  years: "yr"
@@ -6378,7 +7409,7 @@ function LeaseServiceCard({
6378
7409
  const image = product.image_url || product.images?.[0] || "";
6379
7410
  const hasDeposit = product.deposit_type && product.deposit_type !== "none" && product.deposit_amount;
6380
7411
  const hasTiers = product.quantity_pricing && product.quantity_pricing.length > 1;
6381
- const unit = product.duration_unit ? UNIT_LABELS2[product.duration_unit] || product.duration_unit : "mo";
7412
+ const unit = product.duration_unit ? UNIT_LABELS3[product.duration_unit] || product.duration_unit : "mo";
6382
7413
  const tags = product.tags || [];
6383
7414
  const cancellationHours = product.cancellation_window_minutes ? Math.floor(product.cancellation_window_minutes / 60) : null;
6384
7415
  return /* @__PURE__ */ jsxs(CardShell, { product, renderLink, className, children: [
@@ -6436,6 +7467,7 @@ function LeaseServiceCard({
6436
7467
  }
6437
7468
  var FREQUENCY_LABELS2 = {
6438
7469
  weekly: "/week",
7470
+ biweekly: "/2wk",
6439
7471
  monthly: "/mo",
6440
7472
  quarterly: "/qtr",
6441
7473
  annually: "/yr"
@@ -6741,13 +7773,13 @@ function ProductGrid({
6741
7773
  className,
6742
7774
  classNames
6743
7775
  }) {
6744
- const rawId = React3.useId();
7776
+ const rawId = React10.useId();
6745
7777
  const gridId = `cimplify-grid-${rawId.replace(/:/g, "")}`;
6746
7778
  const sm = columns?.sm ?? 1;
6747
7779
  const md = columns?.md ?? 2;
6748
7780
  const lg = columns?.lg ?? 3;
6749
7781
  const xl = columns?.xl ?? 4;
6750
- const css = React3.useMemo(
7782
+ const css = React10.useMemo(
6751
7783
  () => [
6752
7784
  `#${gridId}{display:grid;grid-template-columns:repeat(${sm},1fr);gap:1rem}`,
6753
7785
  `@media(min-width:768px){#${gridId}{grid-template-columns:repeat(${md},1fr)}}`,
@@ -7108,7 +8140,7 @@ function CategoryGrid({
7108
8140
  enabled: categoriesProp === void 0
7109
8141
  });
7110
8142
  const categories = categoriesProp ?? fetched;
7111
- const rawId = React3.useId();
8143
+ const rawId = React10.useId();
7112
8144
  const gridId = `cimplify-cat-grid-${rawId.replace(/:/g, "")}`;
7113
8145
  const sm = columns?.sm ?? 2;
7114
8146
  const md = columns?.md ?? 3;
@@ -7334,7 +8366,7 @@ function OrderSummary({
7334
8366
  /* @__PURE__ */ jsx("p", { children: order.delivery_address })
7335
8367
  ] }),
7336
8368
  /* @__PURE__ */ jsx("div", { "data-cimplify-order-items": true, className: classNames?.items, children: order.items.map(
7337
- (item) => renderLineItem ? /* @__PURE__ */ jsx(React3.Fragment, { children: renderLineItem(item) }, item.id) : /* @__PURE__ */ jsxs("div", { "data-cimplify-order-line-item": true, className: classNames?.lineItem, children: [
8369
+ (item) => renderLineItem ? /* @__PURE__ */ jsx(React10.Fragment, { children: renderLineItem(item) }, item.id) : /* @__PURE__ */ jsxs("div", { "data-cimplify-order-line-item": true, className: classNames?.lineItem, children: [
7338
8370
  /* @__PURE__ */ jsxs("div", { "data-cimplify-order-line-info": true, children: [
7339
8371
  /* @__PURE__ */ jsxs("span", { "data-cimplify-order-line-qty": true, children: [
7340
8372
  item.quantity,
@@ -8315,7 +9347,7 @@ function DefaultCatalogueLayout({
8315
9347
  /* @__PURE__ */ jsx("p", { className: "text-lg font-medium", children: "No products found" }),
8316
9348
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Try adjusting your filters" })
8317
9349
  ] }) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-4", children: products.map(
8318
- (product) => renderCard ? /* @__PURE__ */ jsx(React3.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
9350
+ (product) => renderCard ? /* @__PURE__ */ jsx(React10.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
8319
9351
  ProductCard,
8320
9352
  {
8321
9353
  product,
@@ -8422,7 +9454,7 @@ function CompactCatalogueLayout({
8422
9454
  /* @__PURE__ */ jsx("p", { className: "text-lg font-medium", children: "No items found" }),
8423
9455
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Try a different category or search" })
8424
9456
  ] }) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 lg:grid-cols-3 gap-5", children: products.map(
8425
- (product) => renderCard ? /* @__PURE__ */ jsx(React3.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
9457
+ (product) => renderCard ? /* @__PURE__ */ jsx(React10.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
8426
9458
  ProductCard,
8427
9459
  {
8428
9460
  product,
@@ -8445,8 +9477,8 @@ var BUILT_IN_LAYOUTS = {
8445
9477
  ["compact" /* Compact */]: CompactCatalogueLayout
8446
9478
  };
8447
9479
  function useDebounce(value, delay) {
8448
- const [debounced, setDebounced] = React3.useState(value);
8449
- React3.useEffect(() => {
9480
+ const [debounced, setDebounced] = React10.useState(value);
9481
+ React10.useEffect(() => {
8450
9482
  const timer = setTimeout(() => setDebounced(value), delay);
8451
9483
  return () => clearTimeout(timer);
8452
9484
  }, [value, delay]);
@@ -8938,15 +9970,6 @@ function WholesaleProductLayout({
8938
9970
  )
8939
9971
  ] });
8940
9972
  }
8941
- function formatMinutes(minutes) {
8942
- if (minutes >= 1440) return `${Math.floor(minutes / 1440)} day${minutes >= 2880 ? "s" : ""}`;
8943
- if (minutes >= 60) {
8944
- const h = Math.floor(minutes / 60);
8945
- const m = minutes % 60;
8946
- return m > 0 ? `${h}h ${m}min` : `${h} hour${h > 1 ? "s" : ""}`;
8947
- }
8948
- return `${minutes} min`;
8949
- }
8950
9973
  function ServiceProductLayout({
8951
9974
  product,
8952
9975
  onAddToCart,
@@ -8978,7 +10001,7 @@ function ServiceProductLayout({
8978
10001
  /* @__PURE__ */ jsx(Price, { amount: product.default_price, className: "text-2xl font-bold text-primary mt-2 block" })
8979
10002
  ] }),
8980
10003
  /* @__PURE__ */ jsxs("div", { "data-cimplify-product-layout-service-info": true, className: "flex flex-wrap gap-2", children: [
8981
- product.duration_minutes != null && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center gap-1.5 px-3 py-1.5 bg-muted text-sm text-muted-foreground rounded-full", children: formatMinutes(product.duration_minutes) }),
10004
+ product.duration_minutes != null && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center gap-1.5 px-3 py-1.5 bg-muted text-sm text-muted-foreground rounded-full", children: formatDuration2(product.duration_minutes, product.duration_unit) }),
8982
10005
  hasDeposit && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 px-3 py-1.5 bg-muted text-sm text-muted-foreground rounded-full", children: [
8983
10006
  /* @__PURE__ */ jsx(Price, { amount: depositAmount }),
8984
10007
  " deposit"
@@ -9015,7 +10038,7 @@ function ServiceProductLayout({
9015
10038
  cancellationHours,
9016
10039
  " hours"
9017
10040
  ] }),
9018
- " before the appointment"
10041
+ " before the booking"
9019
10042
  ] }),
9020
10043
  product.no_show_fee && /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground", children: [
9021
10044
  "No-show fee: ",
@@ -9716,7 +10739,7 @@ function DefaultCollectionLayout({
9716
10739
  ] })
9717
10740
  ] }),
9718
10741
  filtered.length > 0 ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4", children: filtered.map(
9719
- (product) => renderCard ? /* @__PURE__ */ jsx(React3.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
10742
+ (product) => renderCard ? /* @__PURE__ */ jsx(React10.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
9720
10743
  ProductCard,
9721
10744
  {
9722
10745
  product,
@@ -9769,7 +10792,7 @@ function FeaturedCollectionLayout({
9769
10792
  ] })
9770
10793
  ] }),
9771
10794
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: products.map(
9772
- (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(
9773
10796
  ProductCard,
9774
10797
  {
9775
10798
  product,
@@ -9893,7 +10916,7 @@ function CatalogueCollectionLayout({
9893
10916
  ] })
9894
10917
  ] }),
9895
10918
  filtered.length > 0 ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4", children: filtered.map(
9896
- (product) => renderCard ? /* @__PURE__ */ jsx(React3.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
10919
+ (product) => renderCard ? /* @__PURE__ */ jsx(React10.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
9897
10920
  ProductCard,
9898
10921
  {
9899
10922
  product,
@@ -10273,7 +11296,7 @@ function DefaultSearchLayout({
10273
11296
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Try a different search term" }),
10274
11297
  /* @__PURE__ */ jsx("button", { onClick: clear, className: "text-sm font-medium text-primary mt-4 hover:underline", children: "Clear search" })
10275
11298
  ] }) : results.length > 0 ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-4", children: results.map(
10276
- (product) => renderCard ? /* @__PURE__ */ jsx(React3.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
11299
+ (product) => renderCard ? /* @__PURE__ */ jsx(React10.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
10277
11300
  ProductCard,
10278
11301
  {
10279
11302
  product,
@@ -10375,7 +11398,7 @@ function CompactSearchLayout({
10375
11398
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Try a different search term" }),
10376
11399
  /* @__PURE__ */ jsx("button", { onClick: clear, className: "text-sm font-medium text-primary mt-4 hover:underline", children: "Clear search" })
10377
11400
  ] }) : results.length > 0 ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 lg:grid-cols-3 gap-5", children: results.map(
10378
- (product) => renderCard ? /* @__PURE__ */ jsx(React3.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
11401
+ (product) => renderCard ? /* @__PURE__ */ jsx(React10.Fragment, { children: renderCard(product) }, product.id) : /* @__PURE__ */ jsx(
10379
11402
  ProductCard,
10380
11403
  {
10381
11404
  product,
@@ -10939,5 +11962,491 @@ function ResourcePicker({
10939
11962
  }
10940
11963
  );
10941
11964
  }
11965
+ function ChatWidget({
11966
+ client: clientProp,
11967
+ businessName = "Support",
11968
+ greeting = "Hi there!",
11969
+ subtitle = "Ask us anything \u2014 we typically reply in a few seconds.",
11970
+ starters,
11971
+ placeholder = "Type a message\u2026",
11972
+ position = "bottom-right",
11973
+ defaultOpen = false,
11974
+ pollInterval,
11975
+ className,
11976
+ classNames
11977
+ }) {
11978
+ const context = useOptionalCimplifyClient();
11979
+ const client = clientProp ?? context?.client;
11980
+ const [open, setOpen] = useState(defaultOpen);
11981
+ const [unread, setUnread] = useState(0);
11982
+ const { messages, isLoading, isSending, error, isActive, send, startConversation } = useChat({ client, pollInterval });
11983
+ const prevCountRef = useRef(messages.length);
11984
+ useEffect(() => {
11985
+ if (!open && messages.length > prevCountRef.current) {
11986
+ const newMessages = messages.slice(prevCountRef.current);
11987
+ const incomingCount = newMessages.filter(
11988
+ (m) => m.sender_type !== "customer"
11989
+ ).length;
11990
+ if (incomingCount > 0) setUnread((n) => n + incomingCount);
11991
+ }
11992
+ prevCountRef.current = messages.length;
11993
+ }, [messages, open]);
11994
+ const handleOpen = useCallback(() => {
11995
+ setOpen(true);
11996
+ setUnread(0);
11997
+ }, []);
11998
+ const handleClose = useCallback(() => setOpen(false), []);
11999
+ const isLeft = position === "bottom-left";
12000
+ return /* @__PURE__ */ jsxs(
12001
+ "div",
12002
+ {
12003
+ className: cn(
12004
+ "fixed bottom-6 z-[9999]",
12005
+ isLeft ? "left-6" : "right-6",
12006
+ className,
12007
+ classNames?.root
12008
+ ),
12009
+ children: [
12010
+ !open && /* @__PURE__ */ jsx(
12011
+ Bubble,
12012
+ {
12013
+ unread,
12014
+ onClick: handleOpen,
12015
+ className: classNames?.bubble
12016
+ }
12017
+ ),
12018
+ open && /* @__PURE__ */ jsx(
12019
+ Panel,
12020
+ {
12021
+ businessName,
12022
+ greeting,
12023
+ subtitle,
12024
+ starters,
12025
+ placeholder,
12026
+ messages,
12027
+ isLoading,
12028
+ isSending,
12029
+ error,
12030
+ isActive,
12031
+ isLeft,
12032
+ onClose: handleClose,
12033
+ onSend: send,
12034
+ onStartConversation: startConversation,
12035
+ classNames
12036
+ }
12037
+ )
12038
+ ]
12039
+ }
12040
+ );
12041
+ }
12042
+ function Bubble({
12043
+ unread,
12044
+ onClick,
12045
+ className
12046
+ }) {
12047
+ return /* @__PURE__ */ jsxs(
12048
+ "button",
12049
+ {
12050
+ type: "button",
12051
+ onClick,
12052
+ "aria-label": "Open chat",
12053
+ className: cn(
12054
+ "relative flex h-14 w-14 items-center justify-center rounded-full",
12055
+ "bg-foreground text-background shadow-lg",
12056
+ "transition-transform duration-300 ease-out",
12057
+ "hover:scale-[1.08] active:scale-95",
12058
+ className
12059
+ ),
12060
+ children: [
12061
+ /* @__PURE__ */ jsx(ChatIcon, { className: "h-6 w-6" }),
12062
+ unread > 0 && /* @__PURE__ */ jsx(
12063
+ "span",
12064
+ {
12065
+ className: cn(
12066
+ "absolute -right-0.5 -top-0.5 flex h-5 w-5 items-center justify-center",
12067
+ "rounded-full bg-red-500 text-[10px] font-bold text-white",
12068
+ "ring-2 ring-background"
12069
+ ),
12070
+ children: unread > 9 ? "9+" : unread
12071
+ }
12072
+ )
12073
+ ]
12074
+ }
12075
+ );
12076
+ }
12077
+ function Panel({
12078
+ businessName,
12079
+ greeting,
12080
+ subtitle,
12081
+ starters,
12082
+ placeholder,
12083
+ messages,
12084
+ isLoading,
12085
+ isSending,
12086
+ error,
12087
+ isActive,
12088
+ isLeft,
12089
+ onClose,
12090
+ onSend,
12091
+ onStartConversation,
12092
+ classNames
12093
+ }) {
12094
+ return /* @__PURE__ */ jsxs(
12095
+ "div",
12096
+ {
12097
+ className: cn(
12098
+ "flex flex-col overflow-hidden rounded-2xl bg-background shadow-2xl",
12099
+ "w-[400px] max-w-[calc(100vw-3rem)]",
12100
+ "h-[min(600px,calc(100vh-6rem))]",
12101
+ "animate-in fade-in slide-in-from-bottom-4 duration-300",
12102
+ isLeft ? "origin-bottom-left" : "origin-bottom-right",
12103
+ classNames?.panel
12104
+ ),
12105
+ children: [
12106
+ /* @__PURE__ */ jsx(
12107
+ Header,
12108
+ {
12109
+ businessName,
12110
+ onClose,
12111
+ className: classNames?.header
12112
+ }
12113
+ ),
12114
+ !isActive && !isLoading ? /* @__PURE__ */ jsx(
12115
+ Welcome,
12116
+ {
12117
+ greeting,
12118
+ subtitle,
12119
+ starters,
12120
+ onStarter: onStartConversation,
12121
+ className: classNames?.welcome
12122
+ }
12123
+ ) : /* @__PURE__ */ jsx(
12124
+ MessageList,
12125
+ {
12126
+ messages,
12127
+ isLoading,
12128
+ isSending,
12129
+ className: classNames?.messages
12130
+ }
12131
+ ),
12132
+ error != null && /* @__PURE__ */ jsx("div", { className: "border-t border-red-200 bg-red-50 px-4 py-2 text-xs text-red-600", children: "Something went wrong. Please try again." }),
12133
+ (isActive || isLoading) && /* @__PURE__ */ jsx(
12134
+ ChatInput,
12135
+ {
12136
+ placeholder,
12137
+ isSending,
12138
+ onSend,
12139
+ className: classNames?.input
12140
+ }
12141
+ ),
12142
+ /* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 border-t border-border bg-background px-4 py-1.5 text-center text-[10px] text-muted-foreground", children: [
12143
+ "Powered by",
12144
+ " ",
12145
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "Cimplify" })
12146
+ ] })
12147
+ ]
12148
+ }
12149
+ );
12150
+ }
12151
+ function Header({
12152
+ businessName,
12153
+ onClose,
12154
+ className
12155
+ }) {
12156
+ return /* @__PURE__ */ jsxs(
12157
+ "div",
12158
+ {
12159
+ className: cn(
12160
+ "flex flex-shrink-0 items-center gap-3 bg-foreground px-5 py-4 text-background",
12161
+ className
12162
+ ),
12163
+ children: [
12164
+ /* @__PURE__ */ jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px] bg-white/15", children: /* @__PURE__ */ jsx(ChatIcon, { className: "h-5 w-5" }) }),
12165
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
12166
+ /* @__PURE__ */ jsx("div", { className: "truncate text-[15px] font-semibold leading-tight", children: businessName }),
12167
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-white/60", children: [
12168
+ /* @__PURE__ */ jsx("span", { className: "h-[7px] w-[7px] flex-shrink-0 rounded-full bg-emerald-400" }),
12169
+ "Online"
12170
+ ] })
12171
+ ] }),
12172
+ /* @__PURE__ */ jsx(
12173
+ "button",
12174
+ {
12175
+ type: "button",
12176
+ onClick: onClose,
12177
+ "aria-label": "Close chat",
12178
+ className: cn(
12179
+ "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg",
12180
+ "bg-white/10 text-white/70 transition-colors hover:bg-white/20 hover:text-white"
12181
+ ),
12182
+ children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-[18px] w-[18px]" })
12183
+ }
12184
+ )
12185
+ ]
12186
+ }
12187
+ );
12188
+ }
12189
+ function Welcome({
12190
+ greeting,
12191
+ subtitle,
12192
+ starters,
12193
+ onStarter,
12194
+ className
12195
+ }) {
12196
+ const defaultStarters = [
12197
+ { icon: "\u{1F50D}", text: "Help me find a product" },
12198
+ { icon: "\u{1F6D2}", text: "Check my cart" },
12199
+ { icon: "\u{1F4E6}", text: "Track my order" }
12200
+ ];
12201
+ const items = starters ?? defaultStarters;
12202
+ return /* @__PURE__ */ jsxs(
12203
+ "div",
12204
+ {
12205
+ className: cn(
12206
+ "flex flex-1 flex-col items-center justify-center overflow-y-auto bg-muted/50 px-8 py-10 text-center",
12207
+ className
12208
+ ),
12209
+ children: [
12210
+ /* @__PURE__ */ jsx("div", { className: "mb-5 flex h-16 w-16 items-center justify-center rounded-2xl bg-muted", children: /* @__PURE__ */ jsx(ChatIcon, { className: "h-7 w-7 text-muted-foreground" }) }),
12211
+ /* @__PURE__ */ jsx("h3", { className: "mb-2 text-xl font-bold text-foreground", children: greeting }),
12212
+ /* @__PURE__ */ jsx("p", { className: "mb-6 max-w-[280px] text-sm leading-relaxed text-muted-foreground", children: subtitle }),
12213
+ /* @__PURE__ */ jsx("div", { className: "flex w-full max-w-[300px] flex-col gap-2", children: items.map((s) => /* @__PURE__ */ jsxs(
12214
+ "button",
12215
+ {
12216
+ type: "button",
12217
+ onClick: () => onStarter(s.text),
12218
+ className: cn(
12219
+ "flex items-center gap-3 rounded-xl border border-border bg-background px-4 py-3",
12220
+ "text-left text-[13px] font-medium text-foreground",
12221
+ "transition-all hover:-translate-y-px hover:border-foreground hover:shadow-sm"
12222
+ ),
12223
+ children: [
12224
+ s.icon && /* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-muted text-[15px]", children: s.icon }),
12225
+ /* @__PURE__ */ jsx("span", { children: s.text })
12226
+ ]
12227
+ },
12228
+ s.text
12229
+ )) })
12230
+ ]
12231
+ }
12232
+ );
12233
+ }
12234
+ function MessageList({
12235
+ messages,
12236
+ isLoading,
12237
+ isSending,
12238
+ className
12239
+ }) {
12240
+ const bottomRef = useRef(null);
12241
+ useEffect(() => {
12242
+ bottomRef.current?.scrollIntoView({ behavior: "smooth" });
12243
+ }, [messages.length, isSending]);
12244
+ return /* @__PURE__ */ jsxs(
12245
+ "div",
12246
+ {
12247
+ className: cn(
12248
+ "flex flex-1 flex-col gap-1 overflow-y-auto bg-muted/50 px-5 py-5",
12249
+ className
12250
+ ),
12251
+ children: [
12252
+ isLoading && messages.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
12253
+ messages.map((msg, i) => {
12254
+ const isCustomer = msg.sender_type === "customer";
12255
+ const prev = messages[i - 1];
12256
+ const sameSenderAsPrev = prev?.sender_type === msg.sender_type;
12257
+ const isOptimistic = msg.id.startsWith("opt_");
12258
+ return /* @__PURE__ */ jsxs("div", { children: [
12259
+ !sameSenderAsPrev && i > 0 && /* @__PURE__ */ jsx("div", { className: "h-3" }),
12260
+ /* @__PURE__ */ jsx(
12261
+ "div",
12262
+ {
12263
+ className: cn(
12264
+ "flex max-w-[85%]",
12265
+ isCustomer ? "ml-auto" : "mr-auto"
12266
+ ),
12267
+ children: /* @__PURE__ */ jsxs(
12268
+ "div",
12269
+ {
12270
+ className: cn(
12271
+ "rounded-2xl px-3.5 py-2.5 text-sm leading-relaxed",
12272
+ isCustomer ? "bg-foreground text-background" : "border border-border bg-background text-foreground",
12273
+ isCustomer ? sameSenderAsPrev ? "rounded-tr" : "rounded-br" : sameSenderAsPrev ? "rounded-tl" : "rounded-bl",
12274
+ isOptimistic && "opacity-70"
12275
+ ),
12276
+ children: [
12277
+ !isCustomer && !sameSenderAsPrev && /* @__PURE__ */ jsxs("span", { className: "mb-1 inline-flex items-center gap-1 rounded bg-violet-100 px-1.5 py-0.5 text-[10px] font-semibold text-violet-600", children: [
12278
+ /* @__PURE__ */ jsx(SparkleIcon, { className: "h-[11px] w-[11px]" }),
12279
+ "AI"
12280
+ ] }),
12281
+ /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap", children: msg.content })
12282
+ ]
12283
+ }
12284
+ )
12285
+ }
12286
+ )
12287
+ ] }, msg.id);
12288
+ }),
12289
+ isSending && /* @__PURE__ */ jsx("div", { className: "mr-auto mt-1", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
12290
+ /* @__PURE__ */ jsx("div", { ref: bottomRef })
12291
+ ]
12292
+ }
12293
+ );
12294
+ }
12295
+ function ChatInput({
12296
+ placeholder,
12297
+ isSending,
12298
+ onSend,
12299
+ className
12300
+ }) {
12301
+ const [value, setValue] = useState("");
12302
+ const textareaRef = useRef(null);
12303
+ const handleSend = useCallback(() => {
12304
+ const trimmed = value.trim();
12305
+ if (!trimmed || isSending) return;
12306
+ setValue("");
12307
+ if (textareaRef.current) {
12308
+ textareaRef.current.style.height = "auto";
12309
+ }
12310
+ onSend(trimmed);
12311
+ }, [value, isSending, onSend]);
12312
+ const handleKeyDown = useCallback(
12313
+ (e) => {
12314
+ if (e.key === "Enter" && !e.shiftKey) {
12315
+ e.preventDefault();
12316
+ handleSend();
12317
+ }
12318
+ },
12319
+ [handleSend]
12320
+ );
12321
+ const handleInput = useCallback(() => {
12322
+ const el = textareaRef.current;
12323
+ if (!el) return;
12324
+ el.style.height = "auto";
12325
+ el.style.height = `${Math.min(el.scrollHeight, 100)}px`;
12326
+ }, []);
12327
+ const hasContent = value.trim().length > 0;
12328
+ return /* @__PURE__ */ jsxs(
12329
+ "div",
12330
+ {
12331
+ className: cn(
12332
+ "flex flex-shrink-0 items-end gap-2 border-t border-border bg-background px-4 py-3",
12333
+ className
12334
+ ),
12335
+ children: [
12336
+ /* @__PURE__ */ jsx(
12337
+ "div",
12338
+ {
12339
+ className: cn(
12340
+ "flex flex-1 items-end rounded-xl border-[1.5px] border-transparent bg-muted px-1",
12341
+ "transition-colors focus-within:border-foreground focus-within:bg-background"
12342
+ ),
12343
+ children: /* @__PURE__ */ jsx(
12344
+ "textarea",
12345
+ {
12346
+ ref: textareaRef,
12347
+ value,
12348
+ onChange: (e) => setValue(e.target.value),
12349
+ onKeyDown: handleKeyDown,
12350
+ onInput: handleInput,
12351
+ placeholder,
12352
+ rows: 1,
12353
+ className: cn(
12354
+ "max-h-[100px] min-h-[20px] flex-1 resize-none bg-transparent px-2.5 py-2",
12355
+ "text-sm text-foreground outline-none placeholder:text-muted-foreground"
12356
+ )
12357
+ }
12358
+ )
12359
+ }
12360
+ ),
12361
+ /* @__PURE__ */ jsx(
12362
+ "button",
12363
+ {
12364
+ type: "button",
12365
+ onClick: handleSend,
12366
+ disabled: !hasContent || isSending,
12367
+ "aria-label": "Send message",
12368
+ className: cn(
12369
+ "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-[10px]",
12370
+ "transition-all duration-200",
12371
+ hasContent && !isSending ? "bg-foreground text-background hover:scale-105 active:scale-95" : "bg-muted text-muted-foreground"
12372
+ ),
12373
+ children: /* @__PURE__ */ jsx(SendIcon, { className: "h-[18px] w-[18px]" })
12374
+ }
12375
+ )
12376
+ ]
12377
+ }
12378
+ );
12379
+ }
12380
+ function TypingDots() {
12381
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 px-4 py-3", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(
12382
+ "span",
12383
+ {
12384
+ className: "h-[7px] w-[7px] animate-bounce rounded-full bg-muted-foreground/40",
12385
+ style: { animationDelay: `${i * 160}ms` }
12386
+ },
12387
+ i
12388
+ )) });
12389
+ }
12390
+ function ChatIcon({ className }) {
12391
+ return /* @__PURE__ */ jsx(
12392
+ "svg",
12393
+ {
12394
+ className,
12395
+ viewBox: "0 0 24 24",
12396
+ fill: "none",
12397
+ stroke: "currentColor",
12398
+ strokeWidth: 2,
12399
+ strokeLinecap: "round",
12400
+ strokeLinejoin: "round",
12401
+ children: /* @__PURE__ */ jsx("path", { d: "M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z" })
12402
+ }
12403
+ );
12404
+ }
12405
+ function CloseIcon({ className }) {
12406
+ return /* @__PURE__ */ jsx(
12407
+ "svg",
12408
+ {
12409
+ className,
12410
+ viewBox: "0 0 24 24",
12411
+ fill: "none",
12412
+ stroke: "currentColor",
12413
+ strokeWidth: 2,
12414
+ strokeLinecap: "round",
12415
+ strokeLinejoin: "round",
12416
+ children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" })
12417
+ }
12418
+ );
12419
+ }
12420
+ function SendIcon({ className }) {
12421
+ return /* @__PURE__ */ jsxs(
12422
+ "svg",
12423
+ {
12424
+ className,
12425
+ viewBox: "0 0 24 24",
12426
+ fill: "none",
12427
+ stroke: "currentColor",
12428
+ strokeWidth: 2,
12429
+ strokeLinecap: "round",
12430
+ strokeLinejoin: "round",
12431
+ children: [
12432
+ /* @__PURE__ */ jsx("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
12433
+ /* @__PURE__ */ jsx("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
12434
+ ]
12435
+ }
12436
+ );
12437
+ }
12438
+ function SparkleIcon({ className }) {
12439
+ return /* @__PURE__ */ jsx(
12440
+ "svg",
12441
+ {
12442
+ className,
12443
+ viewBox: "0 0 24 24",
12444
+ fill: "none",
12445
+ stroke: "currentColor",
12446
+ strokeWidth: 2.5,
12447
+ children: /* @__PURE__ */ jsx("path", { d: "M12 2v4m0 12v4M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83M2 12h4m12 0h4M4.93 19.07l2.83-2.83m8.48-8.48l2.83-2.83" })
12448
+ }
12449
+ );
12450
+ }
10942
12451
 
10943
- 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, 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 };
12452
+ 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, ChatWidget, 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, useChat, 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 };