@cimplify/sdk 0.29.0 → 0.31.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,6 +1,6 @@
1
1
  "use client";
2
- import { DURATION_UNIT, getVariantDisplayName, INPUT_FIELD_TYPE, PRODUCT_TYPE, createCimplifyClient, RENDER_HINT } from './chunk-REXW2PET.mjs';
3
- export { getVariantDisplayName } from './chunk-REXW2PET.mjs';
2
+ import { DURATION_UNIT, getVariantDisplayName, INPUT_FIELD_TYPE, PRODUCT_TYPE, createCimplifyClient, RENDER_HINT } from './chunk-46BDUSJX.mjs';
3
+ export { getVariantDisplayName } from './chunk-46BDUSJX.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
6
  import React10, { createContext, useRef, useState, useCallback, useEffect, useContext, useMemo, useSyncExternalStore, useId } from 'react';
@@ -3310,6 +3310,182 @@ function useBootstrap(options = {}) {
3310
3310
  const { data, isLoading, error, refetch } = bootstrapQuery(options);
3311
3311
  return { bootstrap: data, isLoading, error, refetch };
3312
3312
  }
3313
+ var POLL_INTERVAL_MS = 3e3;
3314
+ var POLL_IDLE_INTERVAL_MS = 15e3;
3315
+ var IDLE_TIMEOUT_MS = 6e4;
3316
+ var WS_MAX_RECONNECT_ATTEMPTS = 10;
3317
+ var WS_PATH = "/api/v1/support/conversation/ws";
3318
+ function useChat(options = {}) {
3319
+ const context = useOptionalCimplifyClient();
3320
+ const client = options.client ?? context?.client;
3321
+ const [messages, setMessages] = useState([]);
3322
+ const [isLoading, setIsLoading] = useState(false);
3323
+ const [isSending, setIsSending] = useState(false);
3324
+ const [error, setError] = useState(null);
3325
+ const [conversationOpened, setConversationOpened] = useState(false);
3326
+ const pollRef = useRef(null);
3327
+ const lastActivityRef = useRef(Date.now());
3328
+ const openedRef = useRef(false);
3329
+ const messagesRef = useRef([]);
3330
+ const clientRef = useRef(client);
3331
+ const wsRef = useRef(null);
3332
+ const wsReconnectAttemptRef = useRef(0);
3333
+ const wsReconnectTimerRef = useRef(null);
3334
+ messagesRef.current = messages;
3335
+ clientRef.current = client;
3336
+ const pollInterval = options.pollInterval ?? POLL_INTERVAL_MS;
3337
+ const pollIdleInterval = options.pollIdleInterval ?? POLL_IDLE_INTERVAL_MS;
3338
+ const appendMessages = useCallback((incoming) => {
3339
+ setMessages((prev) => {
3340
+ const ids = new Set(prev.map((m) => m.id));
3341
+ const fresh = incoming.filter((m) => !ids.has(m.id));
3342
+ return fresh.length > 0 ? [...prev, ...fresh] : prev;
3343
+ });
3344
+ }, []);
3345
+ const pollMessages = useCallback(async () => {
3346
+ const c = clientRef.current;
3347
+ if (!c) return;
3348
+ if (Date.now() - lastActivityRef.current > IDLE_TIMEOUT_MS) return;
3349
+ const last = messagesRef.current[messagesRef.current.length - 1];
3350
+ const result = await c.support.getMessages({
3351
+ after: last?.created_at,
3352
+ limit: 50
3353
+ });
3354
+ if (result.ok && result.value.length > 0) {
3355
+ appendMessages(result.value);
3356
+ }
3357
+ }, [appendMessages]);
3358
+ const startPolling = useCallback(() => {
3359
+ if (pollRef.current) clearInterval(pollRef.current);
3360
+ const interval = Date.now() - lastActivityRef.current > IDLE_TIMEOUT_MS ? pollIdleInterval : pollInterval;
3361
+ pollRef.current = setInterval(pollMessages, interval);
3362
+ }, [pollMessages, pollInterval, pollIdleInterval]);
3363
+ const stopPolling = useCallback(() => {
3364
+ if (pollRef.current) {
3365
+ clearInterval(pollRef.current);
3366
+ pollRef.current = null;
3367
+ }
3368
+ }, []);
3369
+ const connectWebSocket = useCallback(() => {
3370
+ const c = clientRef.current;
3371
+ if (!c || typeof WebSocket === "undefined") {
3372
+ startPolling();
3373
+ return;
3374
+ }
3375
+ if (wsRef.current) {
3376
+ wsRef.current.close();
3377
+ wsRef.current = null;
3378
+ }
3379
+ const url = c.buildWsUrl(WS_PATH);
3380
+ const ws = new WebSocket(url);
3381
+ ws.onopen = () => {
3382
+ wsReconnectAttemptRef.current = 0;
3383
+ stopPolling();
3384
+ };
3385
+ ws.onmessage = (event) => {
3386
+ try {
3387
+ const envelope = JSON.parse(event.data);
3388
+ if (envelope.type === "message.created" && envelope.data) {
3389
+ appendMessages([envelope.data]);
3390
+ }
3391
+ } catch {
3392
+ }
3393
+ };
3394
+ ws.onclose = () => {
3395
+ wsRef.current = null;
3396
+ if (!openedRef.current) return;
3397
+ pollMessages();
3398
+ if (wsReconnectAttemptRef.current < WS_MAX_RECONNECT_ATTEMPTS) {
3399
+ const attempt = wsReconnectAttemptRef.current++;
3400
+ const delay = Math.min(1e3 * Math.pow(2, attempt) + Math.random() * 500, 3e4);
3401
+ wsReconnectTimerRef.current = setTimeout(connectWebSocket, delay);
3402
+ } else {
3403
+ startPolling();
3404
+ }
3405
+ };
3406
+ ws.onerror = () => {
3407
+ };
3408
+ wsRef.current = ws;
3409
+ }, [appendMessages, startPolling, stopPolling, pollMessages]);
3410
+ useEffect(() => {
3411
+ return () => {
3412
+ if (pollRef.current) clearInterval(pollRef.current);
3413
+ if (wsReconnectTimerRef.current) clearTimeout(wsReconnectTimerRef.current);
3414
+ if (wsRef.current) {
3415
+ wsRef.current.onclose = null;
3416
+ wsRef.current.close();
3417
+ }
3418
+ };
3419
+ }, []);
3420
+ const openConversation = useCallback(async () => {
3421
+ if (openedRef.current || !client) return;
3422
+ openedRef.current = true;
3423
+ setIsLoading(true);
3424
+ setError(null);
3425
+ const result = await client.support.openConversation();
3426
+ if (result.ok) {
3427
+ setMessages(result.value.messages);
3428
+ setConversationOpened(true);
3429
+ connectWebSocket();
3430
+ } else {
3431
+ openedRef.current = false;
3432
+ setError(result.error);
3433
+ }
3434
+ setIsLoading(false);
3435
+ }, [client, connectWebSocket]);
3436
+ const send = useCallback(
3437
+ async (content) => {
3438
+ if (!client || !content.trim()) return;
3439
+ setError(null);
3440
+ lastActivityRef.current = Date.now();
3441
+ if (!openedRef.current) {
3442
+ await openConversation();
3443
+ if (!openedRef.current) return;
3444
+ }
3445
+ const optimisticId = `opt_${Date.now()}`;
3446
+ const optimistic = {
3447
+ id: optimisticId,
3448
+ sender_type: "customer",
3449
+ content: content.trim(),
3450
+ content_type: "text",
3451
+ attachments: [],
3452
+ metadata: {},
3453
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
3454
+ };
3455
+ setMessages((prev) => [...prev, optimistic]);
3456
+ setIsSending(true);
3457
+ const result = await client.support.sendMessage(content.trim());
3458
+ if (result.ok) {
3459
+ setMessages(
3460
+ (prev) => prev.map((m) => m.id === optimisticId ? result.value : m)
3461
+ );
3462
+ } else {
3463
+ setMessages((prev) => prev.filter((m) => m.id !== optimisticId));
3464
+ setError(result.error);
3465
+ }
3466
+ setIsSending(false);
3467
+ },
3468
+ [client, openConversation]
3469
+ );
3470
+ const startConversation = useCallback(
3471
+ async (starterText) => {
3472
+ await openConversation();
3473
+ if (starterText) {
3474
+ await send(starterText);
3475
+ }
3476
+ },
3477
+ [openConversation, send]
3478
+ );
3479
+ return {
3480
+ messages,
3481
+ isLoading,
3482
+ isSending,
3483
+ error,
3484
+ isActive: conversationOpened && messages.length > 0,
3485
+ send,
3486
+ startConversation
3487
+ };
3488
+ }
3313
3489
  var ElementsContext = createContext({
3314
3490
  elements: null,
3315
3491
  isReady: false
@@ -4661,8 +4837,26 @@ function CompositeSelector({
4661
4837
  )
4662
4838
  ] });
4663
4839
  }
4840
+
4841
+ // src/react/utils/format-duration.ts
4842
+ var UNIT_LABELS = {
4843
+ minutes: { singular: "min", plural: "min" },
4844
+ hours: { singular: "hr", plural: "hrs" },
4845
+ days: { singular: "day", plural: "days" },
4846
+ nights: { singular: "night", plural: "nights" },
4847
+ weeks: { singular: "week", plural: "weeks" },
4848
+ months: { singular: "month", plural: "months" },
4849
+ years: { singular: "year", plural: "years" }
4850
+ };
4851
+ function formatDuration2(value, unit) {
4852
+ const resolved = unit ?? "minutes";
4853
+ const labels = UNIT_LABELS[resolved];
4854
+ const label = value === 1 ? labels.singular : labels.plural;
4855
+ return `${value} ${label}`;
4856
+ }
4664
4857
  var FREQUENCY_LABELS = {
4665
4858
  weekly: "Weekly",
4859
+ biweekly: "Biweekly",
4666
4860
  monthly: "Monthly",
4667
4861
  quarterly: "Quarterly",
4668
4862
  annually: "Annually"
@@ -5986,9 +6180,7 @@ function ProductCustomizer({
5986
6180
  )) }),
5987
6181
  isService && product.duration_minutes != null && /* @__PURE__ */ jsxs("div", { "data-cimplify-customizer-duration": true, className: cn("text-sm text-muted-foreground", classNames?.duration), children: [
5988
6182
  "Duration: ",
5989
- product.duration_minutes,
5990
- " ",
5991
- product.duration_unit ?? "minutes"
6183
+ formatDuration2(product.duration_minutes, product.duration_unit)
5992
6184
  ] }),
5993
6185
  showSpecialInstructions && !isDigital && /* @__PURE__ */ jsx("div", { "data-cimplify-customizer-special-instructions": true, children: /* @__PURE__ */ jsx(
5994
6186
  "textarea",
@@ -6951,7 +7143,7 @@ function CompositeProductCard({
6951
7143
  ] })
6952
7144
  ] });
6953
7145
  }
6954
- function formatDuration2(minutes, unit) {
7146
+ function formatDuration3(minutes, unit) {
6955
7147
  if (unit && unit !== "minutes") return `${minutes} ${unit}`;
6956
7148
  if (minutes >= 60) {
6957
7149
  const h = Math.floor(minutes / 60);
@@ -6972,7 +7164,7 @@ function StandardServiceCard({
6972
7164
  return /* @__PURE__ */ jsxs(CardShell, { product, renderLink, className, children: [
6973
7165
  /* @__PURE__ */ jsxs(CardImage, { src: image, alt: product.name, aspectRatio: "16/9", renderImage, children: [
6974
7166
  /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-black/50 to-transparent pointer-events-none" }),
6975
- 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) }),
7167
+ 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) }),
6976
7168
  /* @__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 }) })
6977
7169
  ] }),
6978
7170
  /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
@@ -7006,7 +7198,7 @@ function StandardServiceCard({
7006
7198
  ] })
7007
7199
  ] });
7008
7200
  }
7009
- function formatDuration3(minutes, unit) {
7201
+ function formatDuration4(minutes, unit) {
7010
7202
  if (unit && unit !== "minutes") return `${minutes} ${unit}`;
7011
7203
  if (minutes >= 60) {
7012
7204
  const h = Math.floor(minutes / 60);
@@ -7031,7 +7223,7 @@ function CompactServiceCard({
7031
7223
  /* @__PURE__ */ jsx("h3", { className: "text-[14px] font-semibold text-foreground leading-tight truncate", children: product.name }),
7032
7224
  product.description && /* @__PURE__ */ jsx("p", { className: "text-[12px] text-muted-foreground mt-0.5 truncate", children: product.description }),
7033
7225
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-2 flex-wrap", children: [
7034
- 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) }),
7226
+ 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) }),
7035
7227
  /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-[10.5px] font-medium text-emerald-600", children: [
7036
7228
  /* @__PURE__ */ jsx("span", { className: "w-[6px] h-[6px] rounded-full bg-emerald-500" }),
7037
7229
  "Available"
@@ -7071,7 +7263,7 @@ function ScheduleServiceCard({
7071
7263
  }) {
7072
7264
  const image = product.image_url || product.images?.[0] || "";
7073
7265
  const href = `/products/${product.slug}`;
7074
- const durationLabel = product.duration_minutes != null ? `${product.duration_minutes} ${product.duration_unit ?? "min"}` : null;
7266
+ const durationLabel = product.duration_minutes != null ? formatDuration2(product.duration_minutes, product.duration_unit) : null;
7075
7267
  const displaySlots = (slots || []).slice(0, 3);
7076
7268
  const shellClass = cn(
7077
7269
  "group block text-left cursor-pointer bg-card rounded-[14px] overflow-hidden",
@@ -7124,7 +7316,7 @@ function ScheduleServiceCard({
7124
7316
  }
7125
7317
  return /* @__PURE__ */ jsx("a", { href, className: shellClass, "data-cimplify-card": true, children: inner });
7126
7318
  }
7127
- var UNIT_LABELS = {
7319
+ var UNIT_LABELS2 = {
7128
7320
  minutes: "min",
7129
7321
  hours: "hr",
7130
7322
  days: "day",
@@ -7142,7 +7334,7 @@ function RentalServiceCard({
7142
7334
  }) {
7143
7335
  const image = product.image_url || product.images?.[0] || "";
7144
7336
  const hasDeposit = product.deposit_type && product.deposit_type !== "none" && product.deposit_amount;
7145
- const unit = product.duration_unit ? UNIT_LABELS[product.duration_unit] || product.duration_unit : "day";
7337
+ const unit = product.duration_unit ? UNIT_LABELS2[product.duration_unit] || product.duration_unit : "day";
7146
7338
  const status = product.inventory_status;
7147
7339
  return /* @__PURE__ */ jsxs(CardShell, { product, renderLink, className, children: [
7148
7340
  /* @__PURE__ */ jsxs(CardImage, { src: image, alt: product.name, aspectRatio: "16/9", renderImage, children: [
@@ -7262,7 +7454,7 @@ function AccommodationCard({
7262
7454
  ] })
7263
7455
  ] });
7264
7456
  }
7265
- var UNIT_LABELS2 = {
7457
+ var UNIT_LABELS3 = {
7266
7458
  weeks: "week",
7267
7459
  months: "mo",
7268
7460
  years: "yr"
@@ -7277,7 +7469,7 @@ function LeaseServiceCard({
7277
7469
  const image = product.image_url || product.images?.[0] || "";
7278
7470
  const hasDeposit = product.deposit_type && product.deposit_type !== "none" && product.deposit_amount;
7279
7471
  const hasTiers = product.quantity_pricing && product.quantity_pricing.length > 1;
7280
- const unit = product.duration_unit ? UNIT_LABELS2[product.duration_unit] || product.duration_unit : "mo";
7472
+ const unit = product.duration_unit ? UNIT_LABELS3[product.duration_unit] || product.duration_unit : "mo";
7281
7473
  const tags = product.tags || [];
7282
7474
  const cancellationHours = product.cancellation_window_minutes ? Math.floor(product.cancellation_window_minutes / 60) : null;
7283
7475
  return /* @__PURE__ */ jsxs(CardShell, { product, renderLink, className, children: [
@@ -7335,6 +7527,7 @@ function LeaseServiceCard({
7335
7527
  }
7336
7528
  var FREQUENCY_LABELS2 = {
7337
7529
  weekly: "/week",
7530
+ biweekly: "/2wk",
7338
7531
  monthly: "/mo",
7339
7532
  quarterly: "/qtr",
7340
7533
  annually: "/yr"
@@ -9837,15 +10030,6 @@ function WholesaleProductLayout({
9837
10030
  )
9838
10031
  ] });
9839
10032
  }
9840
- function formatMinutes(minutes) {
9841
- if (minutes >= 1440) return `${Math.floor(minutes / 1440)} day${minutes >= 2880 ? "s" : ""}`;
9842
- if (minutes >= 60) {
9843
- const h = Math.floor(minutes / 60);
9844
- const m = minutes % 60;
9845
- return m > 0 ? `${h}h ${m}min` : `${h} hour${h > 1 ? "s" : ""}`;
9846
- }
9847
- return `${minutes} min`;
9848
- }
9849
10033
  function ServiceProductLayout({
9850
10034
  product,
9851
10035
  onAddToCart,
@@ -9877,7 +10061,7 @@ function ServiceProductLayout({
9877
10061
  /* @__PURE__ */ jsx(Price, { amount: product.default_price, className: "text-2xl font-bold text-primary mt-2 block" })
9878
10062
  ] }),
9879
10063
  /* @__PURE__ */ jsxs("div", { "data-cimplify-product-layout-service-info": true, className: "flex flex-wrap gap-2", children: [
9880
- 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) }),
10064
+ 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) }),
9881
10065
  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: [
9882
10066
  /* @__PURE__ */ jsx(Price, { amount: depositAmount }),
9883
10067
  " deposit"
@@ -9914,7 +10098,7 @@ function ServiceProductLayout({
9914
10098
  cancellationHours,
9915
10099
  " hours"
9916
10100
  ] }),
9917
- " before the appointment"
10101
+ " before the booking"
9918
10102
  ] }),
9919
10103
  product.no_show_fee && /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground", children: [
9920
10104
  "No-show fee: ",
@@ -11838,5 +12022,491 @@ function ResourcePicker({
11838
12022
  }
11839
12023
  );
11840
12024
  }
12025
+ function ChatWidget({
12026
+ client: clientProp,
12027
+ businessName = "Support",
12028
+ greeting = "Hi there!",
12029
+ subtitle = "Ask us anything \u2014 we typically reply in a few seconds.",
12030
+ starters,
12031
+ placeholder = "Type a message\u2026",
12032
+ position = "bottom-right",
12033
+ defaultOpen = false,
12034
+ pollInterval,
12035
+ className,
12036
+ classNames
12037
+ }) {
12038
+ const context = useOptionalCimplifyClient();
12039
+ const client = clientProp ?? context?.client;
12040
+ const [open, setOpen] = useState(defaultOpen);
12041
+ const [unread, setUnread] = useState(0);
12042
+ const { messages, isLoading, isSending, error, isActive, send, startConversation } = useChat({ client, pollInterval });
12043
+ const prevCountRef = useRef(messages.length);
12044
+ useEffect(() => {
12045
+ if (!open && messages.length > prevCountRef.current) {
12046
+ const newMessages = messages.slice(prevCountRef.current);
12047
+ const incomingCount = newMessages.filter(
12048
+ (m) => m.sender_type !== "customer"
12049
+ ).length;
12050
+ if (incomingCount > 0) setUnread((n) => n + incomingCount);
12051
+ }
12052
+ prevCountRef.current = messages.length;
12053
+ }, [messages, open]);
12054
+ const handleOpen = useCallback(() => {
12055
+ setOpen(true);
12056
+ setUnread(0);
12057
+ }, []);
12058
+ const handleClose = useCallback(() => setOpen(false), []);
12059
+ const isLeft = position === "bottom-left";
12060
+ return /* @__PURE__ */ jsxs(
12061
+ "div",
12062
+ {
12063
+ className: cn(
12064
+ "fixed bottom-6 z-[9999]",
12065
+ isLeft ? "left-6" : "right-6",
12066
+ className,
12067
+ classNames?.root
12068
+ ),
12069
+ children: [
12070
+ !open && /* @__PURE__ */ jsx(
12071
+ Bubble,
12072
+ {
12073
+ unread,
12074
+ onClick: handleOpen,
12075
+ className: classNames?.bubble
12076
+ }
12077
+ ),
12078
+ open && /* @__PURE__ */ jsx(
12079
+ Panel,
12080
+ {
12081
+ businessName,
12082
+ greeting,
12083
+ subtitle,
12084
+ starters,
12085
+ placeholder,
12086
+ messages,
12087
+ isLoading,
12088
+ isSending,
12089
+ error,
12090
+ isActive,
12091
+ isLeft,
12092
+ onClose: handleClose,
12093
+ onSend: send,
12094
+ onStartConversation: startConversation,
12095
+ classNames
12096
+ }
12097
+ )
12098
+ ]
12099
+ }
12100
+ );
12101
+ }
12102
+ function Bubble({
12103
+ unread,
12104
+ onClick,
12105
+ className
12106
+ }) {
12107
+ return /* @__PURE__ */ jsxs(
12108
+ "button",
12109
+ {
12110
+ type: "button",
12111
+ onClick,
12112
+ "aria-label": "Open chat",
12113
+ className: cn(
12114
+ "relative flex h-14 w-14 items-center justify-center rounded-full",
12115
+ "bg-foreground text-background shadow-lg",
12116
+ "transition-transform duration-300 ease-out",
12117
+ "hover:scale-[1.08] active:scale-95",
12118
+ className
12119
+ ),
12120
+ children: [
12121
+ /* @__PURE__ */ jsx(ChatIcon, { className: "h-6 w-6" }),
12122
+ unread > 0 && /* @__PURE__ */ jsx(
12123
+ "span",
12124
+ {
12125
+ className: cn(
12126
+ "absolute -right-0.5 -top-0.5 flex h-5 w-5 items-center justify-center",
12127
+ "rounded-full bg-red-500 text-[10px] font-bold text-white",
12128
+ "ring-2 ring-background"
12129
+ ),
12130
+ children: unread > 9 ? "9+" : unread
12131
+ }
12132
+ )
12133
+ ]
12134
+ }
12135
+ );
12136
+ }
12137
+ function Panel({
12138
+ businessName,
12139
+ greeting,
12140
+ subtitle,
12141
+ starters,
12142
+ placeholder,
12143
+ messages,
12144
+ isLoading,
12145
+ isSending,
12146
+ error,
12147
+ isActive,
12148
+ isLeft,
12149
+ onClose,
12150
+ onSend,
12151
+ onStartConversation,
12152
+ classNames
12153
+ }) {
12154
+ return /* @__PURE__ */ jsxs(
12155
+ "div",
12156
+ {
12157
+ className: cn(
12158
+ "flex flex-col overflow-hidden rounded-2xl bg-background shadow-2xl",
12159
+ "w-[400px] max-w-[calc(100vw-3rem)]",
12160
+ "h-[min(600px,calc(100vh-6rem))]",
12161
+ "animate-in fade-in slide-in-from-bottom-4 duration-300",
12162
+ isLeft ? "origin-bottom-left" : "origin-bottom-right",
12163
+ classNames?.panel
12164
+ ),
12165
+ children: [
12166
+ /* @__PURE__ */ jsx(
12167
+ Header,
12168
+ {
12169
+ businessName,
12170
+ onClose,
12171
+ className: classNames?.header
12172
+ }
12173
+ ),
12174
+ !isActive && !isLoading ? /* @__PURE__ */ jsx(
12175
+ Welcome,
12176
+ {
12177
+ greeting,
12178
+ subtitle,
12179
+ starters,
12180
+ onStarter: onStartConversation,
12181
+ className: classNames?.welcome
12182
+ }
12183
+ ) : /* @__PURE__ */ jsx(
12184
+ MessageList,
12185
+ {
12186
+ messages,
12187
+ isLoading,
12188
+ isSending,
12189
+ className: classNames?.messages
12190
+ }
12191
+ ),
12192
+ 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." }),
12193
+ (isActive || isLoading) && /* @__PURE__ */ jsx(
12194
+ ChatInput,
12195
+ {
12196
+ placeholder,
12197
+ isSending,
12198
+ onSend,
12199
+ className: classNames?.input
12200
+ }
12201
+ ),
12202
+ /* @__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: [
12203
+ "Powered by",
12204
+ " ",
12205
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "Cimplify" })
12206
+ ] })
12207
+ ]
12208
+ }
12209
+ );
12210
+ }
12211
+ function Header({
12212
+ businessName,
12213
+ onClose,
12214
+ className
12215
+ }) {
12216
+ return /* @__PURE__ */ jsxs(
12217
+ "div",
12218
+ {
12219
+ className: cn(
12220
+ "flex flex-shrink-0 items-center gap-3 bg-foreground px-5 py-4 text-background",
12221
+ className
12222
+ ),
12223
+ children: [
12224
+ /* @__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" }) }),
12225
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
12226
+ /* @__PURE__ */ jsx("div", { className: "truncate text-[15px] font-semibold leading-tight", children: businessName }),
12227
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-white/60", children: [
12228
+ /* @__PURE__ */ jsx("span", { className: "h-[7px] w-[7px] flex-shrink-0 rounded-full bg-emerald-400" }),
12229
+ "Online"
12230
+ ] })
12231
+ ] }),
12232
+ /* @__PURE__ */ jsx(
12233
+ "button",
12234
+ {
12235
+ type: "button",
12236
+ onClick: onClose,
12237
+ "aria-label": "Close chat",
12238
+ className: cn(
12239
+ "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg",
12240
+ "bg-white/10 text-white/70 transition-colors hover:bg-white/20 hover:text-white"
12241
+ ),
12242
+ children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-[18px] w-[18px]" })
12243
+ }
12244
+ )
12245
+ ]
12246
+ }
12247
+ );
12248
+ }
12249
+ function Welcome({
12250
+ greeting,
12251
+ subtitle,
12252
+ starters,
12253
+ onStarter,
12254
+ className
12255
+ }) {
12256
+ const defaultStarters = [
12257
+ { icon: "\u{1F50D}", text: "Help me find a product" },
12258
+ { icon: "\u{1F6D2}", text: "Check my cart" },
12259
+ { icon: "\u{1F4E6}", text: "Track my order" }
12260
+ ];
12261
+ const items = starters ?? defaultStarters;
12262
+ return /* @__PURE__ */ jsxs(
12263
+ "div",
12264
+ {
12265
+ className: cn(
12266
+ "flex flex-1 flex-col items-center justify-center overflow-y-auto bg-muted/50 px-8 py-10 text-center",
12267
+ className
12268
+ ),
12269
+ children: [
12270
+ /* @__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" }) }),
12271
+ /* @__PURE__ */ jsx("h3", { className: "mb-2 text-xl font-bold text-foreground", children: greeting }),
12272
+ /* @__PURE__ */ jsx("p", { className: "mb-6 max-w-[280px] text-sm leading-relaxed text-muted-foreground", children: subtitle }),
12273
+ /* @__PURE__ */ jsx("div", { className: "flex w-full max-w-[300px] flex-col gap-2", children: items.map((s) => /* @__PURE__ */ jsxs(
12274
+ "button",
12275
+ {
12276
+ type: "button",
12277
+ onClick: () => onStarter(s.text),
12278
+ className: cn(
12279
+ "flex items-center gap-3 rounded-xl border border-border bg-background px-4 py-3",
12280
+ "text-left text-[13px] font-medium text-foreground",
12281
+ "transition-all hover:-translate-y-px hover:border-foreground hover:shadow-sm"
12282
+ ),
12283
+ children: [
12284
+ 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 }),
12285
+ /* @__PURE__ */ jsx("span", { children: s.text })
12286
+ ]
12287
+ },
12288
+ s.text
12289
+ )) })
12290
+ ]
12291
+ }
12292
+ );
12293
+ }
12294
+ function MessageList({
12295
+ messages,
12296
+ isLoading,
12297
+ isSending,
12298
+ className
12299
+ }) {
12300
+ const bottomRef = useRef(null);
12301
+ useEffect(() => {
12302
+ bottomRef.current?.scrollIntoView({ behavior: "smooth" });
12303
+ }, [messages.length, isSending]);
12304
+ return /* @__PURE__ */ jsxs(
12305
+ "div",
12306
+ {
12307
+ className: cn(
12308
+ "flex flex-1 flex-col gap-1 overflow-y-auto bg-muted/50 px-5 py-5",
12309
+ className
12310
+ ),
12311
+ children: [
12312
+ isLoading && messages.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
12313
+ messages.map((msg, i) => {
12314
+ const isCustomer = msg.sender_type === "customer";
12315
+ const prev = messages[i - 1];
12316
+ const sameSenderAsPrev = prev?.sender_type === msg.sender_type;
12317
+ const isOptimistic = msg.id.startsWith("opt_");
12318
+ return /* @__PURE__ */ jsxs("div", { children: [
12319
+ !sameSenderAsPrev && i > 0 && /* @__PURE__ */ jsx("div", { className: "h-3" }),
12320
+ /* @__PURE__ */ jsx(
12321
+ "div",
12322
+ {
12323
+ className: cn(
12324
+ "flex max-w-[85%]",
12325
+ isCustomer ? "ml-auto" : "mr-auto"
12326
+ ),
12327
+ children: /* @__PURE__ */ jsxs(
12328
+ "div",
12329
+ {
12330
+ className: cn(
12331
+ "rounded-2xl px-3.5 py-2.5 text-sm leading-relaxed",
12332
+ isCustomer ? "bg-foreground text-background" : "border border-border bg-background text-foreground",
12333
+ isCustomer ? sameSenderAsPrev ? "rounded-tr" : "rounded-br" : sameSenderAsPrev ? "rounded-tl" : "rounded-bl",
12334
+ isOptimistic && "opacity-70"
12335
+ ),
12336
+ children: [
12337
+ !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: [
12338
+ /* @__PURE__ */ jsx(SparkleIcon, { className: "h-[11px] w-[11px]" }),
12339
+ "AI"
12340
+ ] }),
12341
+ /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap", children: msg.content })
12342
+ ]
12343
+ }
12344
+ )
12345
+ }
12346
+ )
12347
+ ] }, msg.id);
12348
+ }),
12349
+ isSending && /* @__PURE__ */ jsx("div", { className: "mr-auto mt-1", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
12350
+ /* @__PURE__ */ jsx("div", { ref: bottomRef })
12351
+ ]
12352
+ }
12353
+ );
12354
+ }
12355
+ function ChatInput({
12356
+ placeholder,
12357
+ isSending,
12358
+ onSend,
12359
+ className
12360
+ }) {
12361
+ const [value, setValue] = useState("");
12362
+ const textareaRef = useRef(null);
12363
+ const handleSend = useCallback(() => {
12364
+ const trimmed = value.trim();
12365
+ if (!trimmed || isSending) return;
12366
+ setValue("");
12367
+ if (textareaRef.current) {
12368
+ textareaRef.current.style.height = "auto";
12369
+ }
12370
+ onSend(trimmed);
12371
+ }, [value, isSending, onSend]);
12372
+ const handleKeyDown = useCallback(
12373
+ (e) => {
12374
+ if (e.key === "Enter" && !e.shiftKey) {
12375
+ e.preventDefault();
12376
+ handleSend();
12377
+ }
12378
+ },
12379
+ [handleSend]
12380
+ );
12381
+ const handleInput = useCallback(() => {
12382
+ const el = textareaRef.current;
12383
+ if (!el) return;
12384
+ el.style.height = "auto";
12385
+ el.style.height = `${Math.min(el.scrollHeight, 100)}px`;
12386
+ }, []);
12387
+ const hasContent = value.trim().length > 0;
12388
+ return /* @__PURE__ */ jsxs(
12389
+ "div",
12390
+ {
12391
+ className: cn(
12392
+ "flex flex-shrink-0 items-end gap-2 border-t border-border bg-background px-4 py-3",
12393
+ className
12394
+ ),
12395
+ children: [
12396
+ /* @__PURE__ */ jsx(
12397
+ "div",
12398
+ {
12399
+ className: cn(
12400
+ "flex flex-1 items-end rounded-xl border-[1.5px] border-transparent bg-muted px-1",
12401
+ "transition-colors focus-within:border-foreground focus-within:bg-background"
12402
+ ),
12403
+ children: /* @__PURE__ */ jsx(
12404
+ "textarea",
12405
+ {
12406
+ ref: textareaRef,
12407
+ value,
12408
+ onChange: (e) => setValue(e.target.value),
12409
+ onKeyDown: handleKeyDown,
12410
+ onInput: handleInput,
12411
+ placeholder,
12412
+ rows: 1,
12413
+ className: cn(
12414
+ "max-h-[100px] min-h-[20px] flex-1 resize-none bg-transparent px-2.5 py-2",
12415
+ "text-sm text-foreground outline-none placeholder:text-muted-foreground"
12416
+ )
12417
+ }
12418
+ )
12419
+ }
12420
+ ),
12421
+ /* @__PURE__ */ jsx(
12422
+ "button",
12423
+ {
12424
+ type: "button",
12425
+ onClick: handleSend,
12426
+ disabled: !hasContent || isSending,
12427
+ "aria-label": "Send message",
12428
+ className: cn(
12429
+ "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-[10px]",
12430
+ "transition-all duration-200",
12431
+ hasContent && !isSending ? "bg-foreground text-background hover:scale-105 active:scale-95" : "bg-muted text-muted-foreground"
12432
+ ),
12433
+ children: /* @__PURE__ */ jsx(SendIcon, { className: "h-[18px] w-[18px]" })
12434
+ }
12435
+ )
12436
+ ]
12437
+ }
12438
+ );
12439
+ }
12440
+ function TypingDots() {
12441
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 px-4 py-3", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(
12442
+ "span",
12443
+ {
12444
+ className: "h-[7px] w-[7px] animate-bounce rounded-full bg-muted-foreground/40",
12445
+ style: { animationDelay: `${i * 160}ms` }
12446
+ },
12447
+ i
12448
+ )) });
12449
+ }
12450
+ function ChatIcon({ className }) {
12451
+ return /* @__PURE__ */ jsx(
12452
+ "svg",
12453
+ {
12454
+ className,
12455
+ viewBox: "0 0 24 24",
12456
+ fill: "none",
12457
+ stroke: "currentColor",
12458
+ strokeWidth: 2,
12459
+ strokeLinecap: "round",
12460
+ strokeLinejoin: "round",
12461
+ 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" })
12462
+ }
12463
+ );
12464
+ }
12465
+ function CloseIcon({ className }) {
12466
+ return /* @__PURE__ */ jsx(
12467
+ "svg",
12468
+ {
12469
+ className,
12470
+ viewBox: "0 0 24 24",
12471
+ fill: "none",
12472
+ stroke: "currentColor",
12473
+ strokeWidth: 2,
12474
+ strokeLinecap: "round",
12475
+ strokeLinejoin: "round",
12476
+ children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" })
12477
+ }
12478
+ );
12479
+ }
12480
+ function SendIcon({ className }) {
12481
+ return /* @__PURE__ */ jsxs(
12482
+ "svg",
12483
+ {
12484
+ className,
12485
+ viewBox: "0 0 24 24",
12486
+ fill: "none",
12487
+ stroke: "currentColor",
12488
+ strokeWidth: 2,
12489
+ strokeLinecap: "round",
12490
+ strokeLinejoin: "round",
12491
+ children: [
12492
+ /* @__PURE__ */ jsx("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
12493
+ /* @__PURE__ */ jsx("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
12494
+ ]
12495
+ }
12496
+ );
12497
+ }
12498
+ function SparkleIcon({ className }) {
12499
+ return /* @__PURE__ */ jsx(
12500
+ "svg",
12501
+ {
12502
+ className,
12503
+ viewBox: "0 0 24 24",
12504
+ fill: "none",
12505
+ stroke: "currentColor",
12506
+ strokeWidth: 2.5,
12507
+ 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" })
12508
+ }
12509
+ );
12510
+ }
11841
12511
 
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 };
12512
+ 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 };