@constructor-io/constructorio-ui-pia 2.10.0 → 2.10.2

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.
@@ -92,7 +92,7 @@ function CioPia(props) {
92
92
  return (react_1.default.createElement("div", { ref: containerRef, className: 'cio-pia-container', "data-testid": 'cio-pia-container' },
93
93
  react_1.default.createElement(constructorio_ui_components_1.RenderPropsWrapper, { props: renderProps, override: children || (componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.reactNode) },
94
94
  react_1.default.createElement("p", { className: 'cio-pia-title', "data-testid": 'cio-pia-title' }, (0, translate_1.translate)('Any questions about this product?', translations)),
95
- react_1.default.createElement(Input_1.default, { onSubmit: handleSubmitQuestion, onFocus: handleInputFocus, value: currentQuestion, translations: translations }),
95
+ react_1.default.createElement(Input_1.default, { onSubmit: handleSubmitQuestion, onFocus: handleInputFocus, value: currentQuestion, translations: translations, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.input }),
96
96
  isLoading && react_1.default.createElement(LoadingSkeleton_1.default, { componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.loading }),
97
97
  !isLoading && error && react_1.default.createElement(ErrorBlock_1.default, { message: (error === null || error === void 0 ? void 0 : error.message) || 'Unexpected error' }),
98
98
  !isLoading && !error && (react_1.default.createElement(react_1.default.Fragment, null,
@@ -4,6 +4,7 @@ const tslib_1 = require("tslib");
4
4
  const react_1 = tslib_1.__importStar(require("react"));
5
5
  const constructorio_ui_components_1 = require("@constructor-io/constructorio-ui-components");
6
6
  const contentTransformers_1 = require("../../utils/contentTransformers");
7
+ const translate_1 = require("../../utils/translate");
7
8
  function HtmlDescription({ product }) {
8
9
  const { description } = product;
9
10
  if (!description)
@@ -44,8 +45,12 @@ function buildMergedOverrides(componentOverrides, priceSectionOverride) {
44
45
  }
45
46
  return Object.assign(Object.assign({}, componentOverrides), { item: Object.assign(Object.assign({}, componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.item), { productCard: Object.assign(Object.assign({}, (_c = componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.item) === null || _c === void 0 ? void 0 : _c.productCard), { content: Object.assign(Object.assign({}, content), defaults) }) }) });
46
47
  }
47
- function PiaCustomCarousel({ items, componentOverrides, callbacks, onResultClick, question, qnaResultId, priceCurrency, }) {
48
+ function PiaCustomCarousel({ items, componentOverrides, callbacks, onResultClick, question, qnaResultId, translations, priceCurrency, }) {
48
49
  const wrapperRef = (0, react_1.useRef)(null);
50
+ const { onAddToCart } = callbacks || {};
51
+ const addToCartHandler = (0, react_1.useCallback)((event, product) => {
52
+ onAddToCart === null || onAddToCart === void 0 ? void 0 : onAddToCart(product, event);
53
+ }, [onAddToCart]);
49
54
  // Determine to use user-defined click handler or default behavior
50
55
  const productClickHandler = (0, react_1.useCallback)((item) => {
51
56
  const position = items.findIndex((i) => i.id === item.id);
@@ -77,7 +82,18 @@ function PiaCustomCarousel({ items, componentOverrides, callbacks, onResultClick
77
82
  };
78
83
  }, [productClickHandler]);
79
84
  const priceSectionOverride = (0, react_1.useMemo)(() => (priceCurrency ? createPriceSectionOverride(priceCurrency) : undefined), [priceCurrency]);
80
- const mergedOverrides = (0, react_1.useMemo)(() => buildMergedOverrides(componentOverrides, priceSectionOverride), [componentOverrides, priceSectionOverride]);
85
+ const mergedOverrides = (0, react_1.useMemo)(() => {
86
+ var _a;
87
+ const baseOverrides = buildMergedOverrides(componentOverrides, priceSectionOverride);
88
+ const cardOverrides = (_a = baseOverrides.item) === null || _a === void 0 ? void 0 : _a.productCard;
89
+ // The carousel never passes onAddToCart down and the card hides the button without it, so
90
+ // re-render the card with the handler. A full card override owns its own layout - skip it.
91
+ if (!onAddToCart || (cardOverrides === null || cardOverrides === void 0 ? void 0 : cardOverrides.reactNode)) {
92
+ return baseOverrides;
93
+ }
94
+ const addToCartText = (0, translate_1.translate)('Add to Cart', translations);
95
+ return Object.assign(Object.assign({}, baseOverrides), { item: Object.assign(Object.assign({}, baseOverrides.item), { productCard: Object.assign(Object.assign({}, cardOverrides), { reactNode: ({ product }) => (react_1.default.createElement(constructorio_ui_components_1.ProductCard, { product: product, className: 'w-full h-full', addToCartText: addToCartText, onAddToCart: addToCartHandler, componentOverrides: cardOverrides })) }) }) });
96
+ }, [componentOverrides, priceSectionOverride, onAddToCart, translations, addToCartHandler]);
81
97
  // If there are no items, do not render the carousel
82
98
  if (items.length === 0) {
83
99
  return null;
@@ -34,7 +34,7 @@ function ConversationHistory({ conversationHistory, isLoading, error, currentIte
34
34
  isLast && !isLoading && error && (react_1.default.createElement(ErrorBlock_1.default, { message: error.message || 'Unexpected error' })),
35
35
  entry.answer && (react_1.default.createElement("div", { className: 'cio-pia-answer-container' },
36
36
  react_1.default.createElement(Answer_1.default, { text: entry.answer, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.answer }),
37
- carouselItems && (react_1.default.createElement(PiaCustomCarousel_1.default, { items: carouselItems, componentOverrides: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.carousel, callbacks: callbacks, onResultClick: onResultClick, question: entry.question, qnaResultId: qnaResultId, priceCurrency: priceCurrency })),
37
+ carouselItems && (react_1.default.createElement(PiaCustomCarousel_1.default, { items: carouselItems, componentOverrides: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.carousel, callbacks: callbacks, onResultClick: onResultClick, question: entry.question, qnaResultId: qnaResultId, translations: translations, priceCurrency: priceCurrency })),
38
38
  isLast && showFeedback && (react_1.default.createElement(Feedback_1.default, { translations: translations, onFeedback: handleFeedback, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.feedback }))))));
39
39
  })),
40
40
  disclaimerPosition === 'bottom' && disclaimer));
@@ -2,41 +2,49 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  const react_1 = tslib_1.__importStar(require("react"));
5
+ const constructorio_ui_components_1 = require("@constructor-io/constructorio-ui-components");
5
6
  const translate_1 = require("../../utils/translate");
6
7
  function SendIcon() {
7
8
  return (react_1.default.createElement("svg", { width: '16', height: '16', viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg' },
8
9
  react_1.default.createElement("g", { id: '.icon-send' },
9
10
  react_1.default.createElement("path", { id: 'Vector', d: 'M14.8934 7.09533L14.8884 7.09314L1.54219 1.55752C1.42993 1.51053 1.30778 1.49211 1.18666 1.50388C1.06554 1.51566 0.949225 1.55728 0.848125 1.62502C0.741311 1.69501 0.653573 1.79045 0.592805 1.90277C0.532037 2.01509 0.500145 2.14075 0.5 2.26845V5.80877C0.50006 5.98335 0.561019 6.15243 0.67237 6.28689C0.783722 6.42134 0.938491 6.51274 1.11 6.54533L8.38906 7.89127C8.41767 7.89669 8.44348 7.91193 8.46205 7.93435C8.48062 7.95677 8.49078 7.98497 8.49078 8.01408C8.49078 8.04319 8.48062 8.07139 8.46205 8.09381C8.44348 8.11623 8.41767 8.13147 8.38906 8.13689L1.11031 9.48283C0.938851 9.51533 0.784092 9.60661 0.67269 9.74094C0.561288 9.87528 0.500219 10.0443 0.5 10.2188V13.7597C0.499917 13.8816 0.530111 14.0017 0.587871 14.1091C0.645632 14.2165 0.729152 14.3079 0.830938 14.375C0.953375 14.4564 1.09706 14.4998 1.24406 14.5C1.34626 14.4999 1.4474 14.4794 1.54156 14.4397L14.8875 8.93564L14.8934 8.93283C15.0731 8.85563 15.2262 8.72745 15.3337 8.56415C15.4413 8.40086 15.4986 8.20961 15.4986 8.01408C15.4986 7.81855 15.4413 7.6273 15.3337 7.46401C15.2262 7.30071 15.0731 7.17253 14.8934 7.09533Z', fill: 'white' }))));
10
11
  }
11
- function Input({ value: providedValue, placeholder, disabled = false, onSubmit, onFocus, translations, }) {
12
+ function Input({ value: providedValue, placeholder, disabled = false, onSubmit, onFocus, translations, componentOverride, }) {
12
13
  const [value, setValue] = (0, react_1.useState)(providedValue || '');
13
14
  (0, react_1.useEffect)(() => {
14
15
  if (providedValue) {
15
16
  setValue(providedValue);
16
17
  }
17
18
  }, [providedValue]);
18
- const handleSubmit = () => {
19
- if (value.trim()) {
20
- onSubmit(value.trim());
21
- if (value.trim() !== providedValue) {
19
+ const handleSubmit = (0, react_1.useCallback)((submittedValue) => {
20
+ if (submittedValue.trim()) {
21
+ onSubmit(submittedValue.trim());
22
+ if (submittedValue.trim() !== providedValue) {
22
23
  setValue('');
23
24
  }
24
25
  }
25
- };
26
+ }, [onSubmit, providedValue]);
26
27
  const handleSubmitOnEnter = (e) => {
27
28
  if (e.key === 'Enter') {
28
29
  e.preventDefault();
29
- handleSubmit();
30
+ handleSubmit(value);
30
31
  }
31
32
  };
32
33
  // Priority: translations > placeholder prop > default
33
34
  const resolvedPlaceholder = (translations === null || translations === void 0 ? void 0 : translations['Ask anything']) !== undefined
34
35
  ? (0, translate_1.translate)('Ask anything', translations)
35
36
  : (placeholder !== null && placeholder !== void 0 ? placeholder : (0, translate_1.translate)('Ask anything'));
36
- return (react_1.default.createElement("div", { className: 'cio-pia-input-container' },
37
- react_1.default.createElement("input", { type: 'text', value: value, onChange: (e) => setValue(e.target.value), onKeyDown: handleSubmitOnEnter, onFocus: onFocus, placeholder: resolvedPlaceholder, disabled: disabled, className: 'cio-pia-input' }),
38
- react_1.default.createElement("button", { type: 'button', onClick: handleSubmit, className: 'cio-pia-send-button', disabled: disabled },
39
- (0, translate_1.translate)('Send', translations),
40
- react_1.default.createElement(SendIcon, null))));
37
+ return (react_1.default.createElement(constructorio_ui_components_1.RenderPropsWrapper, { props: {
38
+ disabled,
39
+ placeholder: resolvedPlaceholder,
40
+ onSubmit: handleSubmit,
41
+ onFocus,
42
+ translations,
43
+ }, override: componentOverride === null || componentOverride === void 0 ? void 0 : componentOverride.reactNode },
44
+ react_1.default.createElement("div", { className: 'cio-pia-input-container' },
45
+ react_1.default.createElement("input", { type: 'text', value: value, onChange: (e) => setValue(e.target.value), onKeyDown: handleSubmitOnEnter, onFocus: onFocus, placeholder: resolvedPlaceholder, disabled: disabled, className: 'cio-pia-input' }),
46
+ react_1.default.createElement("button", { type: 'button', onClick: () => handleSubmit(value), className: 'cio-pia-send-button', disabled: disabled },
47
+ (0, translate_1.translate)('Send', translations),
48
+ react_1.default.createElement(SendIcon, null)))));
41
49
  }
42
50
  exports.default = Input;
@@ -15,6 +15,6 @@ function PiaConversation({ conversationHistory, isLoading, error, currentItems,
15
15
  react_1.default.createElement("div", { className: 'cio-pia-conversation-footer' },
16
16
  isLoading && !error && react_1.default.createElement(SuggestedQuestionsSkeleton_1.default, null),
17
17
  !isLoading && !error && (react_1.default.createElement(SuggestedQuestionsContainer_1.default, { questions: displayedQuestions, onQuestionClick: handleQuestionClick, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.suggestedQuestions })),
18
- react_1.default.createElement(Input_1.default, { onSubmit: handleSubmitQuestion, onFocus: onInputFocus, disabled: isLoading, translations: translations }))));
18
+ react_1.default.createElement(Input_1.default, { onSubmit: handleSubmitQuestion, onFocus: onInputFocus, disabled: isLoading, translations: translations, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.input }))));
19
19
  }
20
20
  exports.default = PiaConversation;
@@ -67,7 +67,7 @@ function PiaModal({ initialQuestions, handleSubmitQuestion, handleQuestionClick,
67
67
  react_1.default.createElement("p", { className: 'cio-pia-title', "data-testid": 'cio-pia-title' }, (0, translate_1.translate)('Any questions about this product?', translations)),
68
68
  react_1.default.createElement("div", { className: 'cio-pia-conversation-footer' },
69
69
  initialQuestions.length > 0 && (react_1.default.createElement(SuggestedQuestionsContainer_1.default, { questions: initialQuestions, onQuestionClick: handleQuestionClicked, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.suggestedQuestions })),
70
- react_1.default.createElement(Input_1.default, { onSubmit: handleQuestion, onFocus: onInputFocus, disabled: isLoading || isOpen, translations: translations })),
70
+ react_1.default.createElement(Input_1.default, { onSubmit: handleQuestion, onFocus: onInputFocus, disabled: isLoading || isOpen, translations: translations, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.input })),
71
71
  react_1.default.createElement("dialog", { ref: dialogRef, className: 'cio-pia-modal', "aria-labelledby": 'cio-pia-modal-title' },
72
72
  react_1.default.createElement("div", { className: 'cio-pia-modal-content' },
73
73
  react_1.default.createElement("div", { className: 'cio-pia-modal-header' },
@@ -11,7 +11,7 @@ function PiaInlineAnswer({ currentAnswer, currentItems, showFeedback, learnMoreU
11
11
  return (react_1.default.createElement("div", { className: 'cio-pia-answer-container' },
12
12
  disclaimerPosition === 'top' && disclaimer,
13
13
  react_1.default.createElement(Answer_1.default, { text: currentAnswer, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.answer }),
14
- currentItems && (react_1.default.createElement(PiaCustomCarousel_1.default, { items: currentItems, componentOverrides: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.carousel, callbacks: callbacks, onResultClick: onResultClick, question: question, qnaResultId: qnaResultId, priceCurrency: priceCurrency })),
14
+ currentItems && (react_1.default.createElement(PiaCustomCarousel_1.default, { items: currentItems, componentOverrides: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.carousel, callbacks: callbacks, onResultClick: onResultClick, question: question, qnaResultId: qnaResultId, translations: translations, priceCurrency: priceCurrency })),
15
15
  showFeedback && (react_1.default.createElement(Feedback_1.default, { translations: translations, onFeedback: onFeedback || (callbacks === null || callbacks === void 0 ? void 0 : callbacks.onFeedback), componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.feedback })),
16
16
  disclaimerPosition === 'bottom' && disclaimer));
17
17
  }
@@ -6,14 +6,19 @@ const agent_1 = tslib_1.__importDefault(require("./agent"));
6
6
  const version_1 = tslib_1.__importDefault(require("../../version"));
7
7
  class MockConstructorIOClient {
8
8
  constructor(options) {
9
- this.options = Object.assign({ version: options.version || `cio-ui-pia-${version_1.default}`, serviceUrl: options.serviceUrl || 'https://ac.cnstrc.com', quizzesServiceUrl: options.quizzesServiceUrl || 'https://quizzes.cnstrc.com', agentServiceUrl: options.agentServiceUrl || 'https://agent.cnstrc.com', sessionId: options.sessionId || 0, clientId: options.clientId || 'this-is-a-random-client-id', sendTrackingEvents: options.sendTrackingEvents !== undefined ? options.sendTrackingEvents : true, beaconMode: options.beaconMode !== undefined ? options.beaconMode : true, networkParameters: options.networkParameters || {} }, options);
10
- const cioClient = new constructorio_client_javascript_1.default(this.options);
9
+ const isServer = typeof window === 'undefined' || typeof document === 'undefined';
10
+ const clientOptions = Object.assign(Object.assign(Object.assign({ version: options.version || `cio-ui-pia-${version_1.default}`, serviceUrl: options.serviceUrl || 'https://ac.cnstrc.com', quizzesServiceUrl: options.quizzesServiceUrl || 'https://quizzes.cnstrc.com', agentServiceUrl: options.agentServiceUrl || 'https://agent.cnstrc.com' }, (isServer && {
11
+ clientId: options.clientId || 'this-is-a-random-client-id',
12
+ })), { sendTrackingEvents: options.sendTrackingEvents !== undefined ? options.sendTrackingEvents : true, beaconMode: options.beaconMode !== undefined ? options.beaconMode : true, networkParameters: options.networkParameters || {} }), options);
13
+ const cioClient = new constructorio_client_javascript_1.default(clientOptions);
14
+ const { clientId, sessionId } = cioClient.agent.options;
15
+ this.options = Object.assign(Object.assign({}, clientOptions), { clientId, sessionId });
11
16
  this.search = cioClient.search;
12
17
  this.browse = cioClient.browse;
13
18
  this.recommendations = cioClient.recommendations;
14
19
  this.tracker = cioClient.tracker;
15
20
  this.quizzes = cioClient.quizzes;
16
- this.agent = new agent_1.default(Object.assign(Object.assign({}, this.options), { clientId: options.clientId, sessionId: options.sessionId }));
21
+ this.agent = new agent_1.default(this.options);
17
22
  }
18
23
  }
19
24
  exports.default = MockConstructorIOClient;
@@ -10,6 +10,7 @@ const defaultTranslations = {
10
10
  'Is this answer useful?': 'Is this answer useful?',
11
11
  'Learn More.': 'Learn More.',
12
12
  'Ask about this product': 'Ask about this product',
13
+ 'Add to Cart': 'Add to Cart',
13
14
  };
14
15
  /**
15
16
  * Translates a word using the provided translations object.
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = '2.10.0';
3
+ exports.default = '2.10.2';
@@ -88,7 +88,7 @@ export default function CioPia(props) {
88
88
  return (React.createElement("div", { ref: containerRef, className: 'cio-pia-container', "data-testid": 'cio-pia-container' },
89
89
  React.createElement(RenderPropsWrapper, { props: renderProps, override: children || componentOverrides?.reactNode },
90
90
  React.createElement("p", { className: 'cio-pia-title', "data-testid": 'cio-pia-title' }, translate('Any questions about this product?', translations)),
91
- React.createElement(Input, { onSubmit: handleSubmitQuestion, onFocus: handleInputFocus, value: currentQuestion, translations: translations }),
91
+ React.createElement(Input, { onSubmit: handleSubmitQuestion, onFocus: handleInputFocus, value: currentQuestion, translations: translations, componentOverride: componentOverrides?.input }),
92
92
  isLoading && React.createElement(LoadingSkeleton, { componentOverride: componentOverrides?.loading }),
93
93
  !isLoading && error && React.createElement(ErrorBlock, { message: error?.message || 'Unexpected error' }),
94
94
  !isLoading && !error && (React.createElement(React.Fragment, null,
@@ -1,6 +1,7 @@
1
1
  import React, { useCallback, useRef, useEffect, useMemo } from 'react';
2
- import { Carousel, CIO_EVENTS, } from '@constructor-io/constructorio-ui-components';
2
+ import { Carousel, ProductCard, CIO_EVENTS, } from '@constructor-io/constructorio-ui-components';
3
3
  import { sanitizeHtml } from '../../utils/contentTransformers';
4
+ import { translate } from '../../utils/translate';
4
5
  function HtmlDescription({ product }) {
5
6
  const { description } = product;
6
7
  if (!description)
@@ -53,8 +54,12 @@ function buildMergedOverrides(componentOverrides, priceSectionOverride) {
53
54
  },
54
55
  };
55
56
  }
56
- export default function PiaCustomCarousel({ items, componentOverrides, callbacks, onResultClick, question, qnaResultId, priceCurrency, }) {
57
+ export default function PiaCustomCarousel({ items, componentOverrides, callbacks, onResultClick, question, qnaResultId, translations, priceCurrency, }) {
57
58
  const wrapperRef = useRef(null);
59
+ const { onAddToCart } = callbacks || {};
60
+ const addToCartHandler = useCallback((event, product) => {
61
+ onAddToCart?.(product, event);
62
+ }, [onAddToCart]);
58
63
  // Determine to use user-defined click handler or default behavior
59
64
  const productClickHandler = useCallback((item) => {
60
65
  const position = items.findIndex((i) => i.id === item.id);
@@ -86,7 +91,26 @@ export default function PiaCustomCarousel({ items, componentOverrides, callbacks
86
91
  };
87
92
  }, [productClickHandler]);
88
93
  const priceSectionOverride = useMemo(() => (priceCurrency ? createPriceSectionOverride(priceCurrency) : undefined), [priceCurrency]);
89
- const mergedOverrides = useMemo(() => buildMergedOverrides(componentOverrides, priceSectionOverride), [componentOverrides, priceSectionOverride]);
94
+ const mergedOverrides = useMemo(() => {
95
+ const baseOverrides = buildMergedOverrides(componentOverrides, priceSectionOverride);
96
+ const cardOverrides = baseOverrides.item?.productCard;
97
+ // The carousel never passes onAddToCart down and the card hides the button without it, so
98
+ // re-render the card with the handler. A full card override owns its own layout - skip it.
99
+ if (!onAddToCart || cardOverrides?.reactNode) {
100
+ return baseOverrides;
101
+ }
102
+ const addToCartText = translate('Add to Cart', translations);
103
+ return {
104
+ ...baseOverrides,
105
+ item: {
106
+ ...baseOverrides.item,
107
+ productCard: {
108
+ ...cardOverrides,
109
+ reactNode: ({ product }) => (React.createElement(ProductCard, { product: product, className: 'w-full h-full', addToCartText: addToCartText, onAddToCart: addToCartHandler, componentOverrides: cardOverrides })),
110
+ },
111
+ },
112
+ };
113
+ }, [componentOverrides, priceSectionOverride, onAddToCart, translations, addToCartHandler]);
90
114
  // If there are no items, do not render the carousel
91
115
  if (items.length === 0) {
92
116
  return null;
@@ -31,7 +31,7 @@ export default function ConversationHistory({ conversationHistory, isLoading, er
31
31
  isLast && !isLoading && error && (React.createElement(ErrorBlock, { message: error.message || 'Unexpected error' })),
32
32
  entry.answer && (React.createElement("div", { className: 'cio-pia-answer-container' },
33
33
  React.createElement(Answer, { text: entry.answer, componentOverride: componentOverrides?.answer }),
34
- carouselItems && (React.createElement(PiaCustomCarousel, { items: carouselItems, componentOverrides: componentOverrides?.carousel, callbacks: callbacks, onResultClick: onResultClick, question: entry.question, qnaResultId: qnaResultId, priceCurrency: priceCurrency })),
34
+ carouselItems && (React.createElement(PiaCustomCarousel, { items: carouselItems, componentOverrides: componentOverrides?.carousel, callbacks: callbacks, onResultClick: onResultClick, question: entry.question, qnaResultId: qnaResultId, translations: translations, priceCurrency: priceCurrency })),
35
35
  isLast && showFeedback && (React.createElement(Feedback, { translations: translations, onFeedback: handleFeedback, componentOverride: componentOverrides?.feedback }))))));
36
36
  })),
37
37
  disclaimerPosition === 'bottom' && disclaimer));
@@ -1,39 +1,47 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import { RenderPropsWrapper, } from '@constructor-io/constructorio-ui-components';
2
3
  import { translate } from '../../utils/translate';
3
4
  function SendIcon() {
4
5
  return (React.createElement("svg", { width: '16', height: '16', viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg' },
5
6
  React.createElement("g", { id: '.icon-send' },
6
7
  React.createElement("path", { id: 'Vector', d: 'M14.8934 7.09533L14.8884 7.09314L1.54219 1.55752C1.42993 1.51053 1.30778 1.49211 1.18666 1.50388C1.06554 1.51566 0.949225 1.55728 0.848125 1.62502C0.741311 1.69501 0.653573 1.79045 0.592805 1.90277C0.532037 2.01509 0.500145 2.14075 0.5 2.26845V5.80877C0.50006 5.98335 0.561019 6.15243 0.67237 6.28689C0.783722 6.42134 0.938491 6.51274 1.11 6.54533L8.38906 7.89127C8.41767 7.89669 8.44348 7.91193 8.46205 7.93435C8.48062 7.95677 8.49078 7.98497 8.49078 8.01408C8.49078 8.04319 8.48062 8.07139 8.46205 8.09381C8.44348 8.11623 8.41767 8.13147 8.38906 8.13689L1.11031 9.48283C0.938851 9.51533 0.784092 9.60661 0.67269 9.74094C0.561288 9.87528 0.500219 10.0443 0.5 10.2188V13.7597C0.499917 13.8816 0.530111 14.0017 0.587871 14.1091C0.645632 14.2165 0.729152 14.3079 0.830938 14.375C0.953375 14.4564 1.09706 14.4998 1.24406 14.5C1.34626 14.4999 1.4474 14.4794 1.54156 14.4397L14.8875 8.93564L14.8934 8.93283C15.0731 8.85563 15.2262 8.72745 15.3337 8.56415C15.4413 8.40086 15.4986 8.20961 15.4986 8.01408C15.4986 7.81855 15.4413 7.6273 15.3337 7.46401C15.2262 7.30071 15.0731 7.17253 14.8934 7.09533Z', fill: 'white' }))));
7
8
  }
8
- function Input({ value: providedValue, placeholder, disabled = false, onSubmit, onFocus, translations, }) {
9
+ function Input({ value: providedValue, placeholder, disabled = false, onSubmit, onFocus, translations, componentOverride, }) {
9
10
  const [value, setValue] = useState(providedValue || '');
10
11
  useEffect(() => {
11
12
  if (providedValue) {
12
13
  setValue(providedValue);
13
14
  }
14
15
  }, [providedValue]);
15
- const handleSubmit = () => {
16
- if (value.trim()) {
17
- onSubmit(value.trim());
18
- if (value.trim() !== providedValue) {
16
+ const handleSubmit = useCallback((submittedValue) => {
17
+ if (submittedValue.trim()) {
18
+ onSubmit(submittedValue.trim());
19
+ if (submittedValue.trim() !== providedValue) {
19
20
  setValue('');
20
21
  }
21
22
  }
22
- };
23
+ }, [onSubmit, providedValue]);
23
24
  const handleSubmitOnEnter = (e) => {
24
25
  if (e.key === 'Enter') {
25
26
  e.preventDefault();
26
- handleSubmit();
27
+ handleSubmit(value);
27
28
  }
28
29
  };
29
30
  // Priority: translations > placeholder prop > default
30
31
  const resolvedPlaceholder = translations?.['Ask anything'] !== undefined
31
32
  ? translate('Ask anything', translations)
32
33
  : (placeholder ?? translate('Ask anything'));
33
- return (React.createElement("div", { className: 'cio-pia-input-container' },
34
- React.createElement("input", { type: 'text', value: value, onChange: (e) => setValue(e.target.value), onKeyDown: handleSubmitOnEnter, onFocus: onFocus, placeholder: resolvedPlaceholder, disabled: disabled, className: 'cio-pia-input' }),
35
- React.createElement("button", { type: 'button', onClick: handleSubmit, className: 'cio-pia-send-button', disabled: disabled },
36
- translate('Send', translations),
37
- React.createElement(SendIcon, null))));
34
+ return (React.createElement(RenderPropsWrapper, { props: {
35
+ disabled,
36
+ placeholder: resolvedPlaceholder,
37
+ onSubmit: handleSubmit,
38
+ onFocus,
39
+ translations,
40
+ }, override: componentOverride?.reactNode },
41
+ React.createElement("div", { className: 'cio-pia-input-container' },
42
+ React.createElement("input", { type: 'text', value: value, onChange: (e) => setValue(e.target.value), onKeyDown: handleSubmitOnEnter, onFocus: onFocus, placeholder: resolvedPlaceholder, disabled: disabled, className: 'cio-pia-input' }),
43
+ React.createElement("button", { type: 'button', onClick: () => handleSubmit(value), className: 'cio-pia-send-button', disabled: disabled },
44
+ translate('Send', translations),
45
+ React.createElement(SendIcon, null)))));
38
46
  }
39
47
  export default Input;
@@ -12,5 +12,5 @@ export default function PiaConversation({ conversationHistory, isLoading, error,
12
12
  React.createElement("div", { className: 'cio-pia-conversation-footer' },
13
13
  isLoading && !error && React.createElement(SuggestedQuestionsSkeleton, null),
14
14
  !isLoading && !error && (React.createElement(SuggestedQuestionsContainer, { questions: displayedQuestions, onQuestionClick: handleQuestionClick, componentOverride: componentOverrides?.suggestedQuestions })),
15
- React.createElement(Input, { onSubmit: handleSubmitQuestion, onFocus: onInputFocus, disabled: isLoading, translations: translations }))));
15
+ React.createElement(Input, { onSubmit: handleSubmitQuestion, onFocus: onInputFocus, disabled: isLoading, translations: translations, componentOverride: componentOverrides?.input }))));
16
16
  }
@@ -64,7 +64,7 @@ export default function PiaModal({ initialQuestions, handleSubmitQuestion, handl
64
64
  React.createElement("p", { className: 'cio-pia-title', "data-testid": 'cio-pia-title' }, translate('Any questions about this product?', translations)),
65
65
  React.createElement("div", { className: 'cio-pia-conversation-footer' },
66
66
  initialQuestions.length > 0 && (React.createElement(SuggestedQuestionsContainer, { questions: initialQuestions, onQuestionClick: handleQuestionClicked, componentOverride: componentOverrides?.suggestedQuestions })),
67
- React.createElement(Input, { onSubmit: handleQuestion, onFocus: onInputFocus, disabled: isLoading || isOpen, translations: translations })),
67
+ React.createElement(Input, { onSubmit: handleQuestion, onFocus: onInputFocus, disabled: isLoading || isOpen, translations: translations, componentOverride: componentOverrides?.input })),
68
68
  React.createElement("dialog", { ref: dialogRef, className: 'cio-pia-modal', "aria-labelledby": 'cio-pia-modal-title' },
69
69
  React.createElement("div", { className: 'cio-pia-modal-content' },
70
70
  React.createElement("div", { className: 'cio-pia-modal-header' },
@@ -8,7 +8,7 @@ export default function PiaInlineAnswer({ currentAnswer, currentItems, showFeedb
8
8
  return (React.createElement("div", { className: 'cio-pia-answer-container' },
9
9
  disclaimerPosition === 'top' && disclaimer,
10
10
  React.createElement(Answer, { text: currentAnswer, componentOverride: componentOverrides?.answer }),
11
- currentItems && (React.createElement(PiaCustomCarousel, { items: currentItems, componentOverrides: componentOverrides?.carousel, callbacks: callbacks, onResultClick: onResultClick, question: question, qnaResultId: qnaResultId, priceCurrency: priceCurrency })),
11
+ currentItems && (React.createElement(PiaCustomCarousel, { items: currentItems, componentOverrides: componentOverrides?.carousel, callbacks: callbacks, onResultClick: onResultClick, question: question, qnaResultId: qnaResultId, translations: translations, priceCurrency: priceCurrency })),
12
12
  showFeedback && (React.createElement(Feedback, { translations: translations, onFeedback: onFeedback || callbacks?.onFeedback, componentOverride: componentOverrides?.feedback })),
13
13
  disclaimerPosition === 'bottom' && disclaimer));
14
14
  }
@@ -10,29 +10,29 @@ class MockConstructorIOClient {
10
10
  quizzes;
11
11
  agent;
12
12
  constructor(options) {
13
- this.options = {
13
+ const isServer = typeof window === 'undefined' || typeof document === 'undefined';
14
+ const clientOptions = {
14
15
  version: options.version || `cio-ui-pia-${version}`,
15
16
  serviceUrl: options.serviceUrl || 'https://ac.cnstrc.com',
16
17
  quizzesServiceUrl: options.quizzesServiceUrl || 'https://quizzes.cnstrc.com',
17
18
  agentServiceUrl: options.agentServiceUrl || 'https://agent.cnstrc.com',
18
- sessionId: options.sessionId || 0,
19
- clientId: options.clientId || 'this-is-a-random-client-id',
19
+ ...(isServer && {
20
+ clientId: options.clientId || 'this-is-a-random-client-id',
21
+ }),
20
22
  sendTrackingEvents: options.sendTrackingEvents !== undefined ? options.sendTrackingEvents : true,
21
23
  beaconMode: options.beaconMode !== undefined ? options.beaconMode : true,
22
24
  networkParameters: options.networkParameters || {},
23
25
  ...options,
24
26
  };
25
- const cioClient = new ConstructorioClient(this.options);
27
+ const cioClient = new ConstructorioClient(clientOptions);
28
+ const { clientId, sessionId } = cioClient.agent.options;
29
+ this.options = { ...clientOptions, clientId, sessionId };
26
30
  this.search = cioClient.search;
27
31
  this.browse = cioClient.browse;
28
32
  this.recommendations = cioClient.recommendations;
29
33
  this.tracker = cioClient.tracker;
30
34
  this.quizzes = cioClient.quizzes;
31
- this.agent = new MockAgent({
32
- ...this.options,
33
- clientId: options.clientId,
34
- sessionId: options.sessionId,
35
- });
35
+ this.agent = new MockAgent(this.options);
36
36
  }
37
37
  }
38
38
  export default MockConstructorIOClient;
@@ -7,6 +7,7 @@ const defaultTranslations = {
7
7
  'Is this answer useful?': 'Is this answer useful?',
8
8
  'Learn More.': 'Learn More.',
9
9
  'Ask about this product': 'Ask about this product',
10
+ 'Add to Cart': 'Add to Cart',
10
11
  };
11
12
  /**
12
13
  * Translates a word using the provided translations object.
@@ -1 +1 @@
1
- export default '2.10.0';
1
+ export default '2.10.2';
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { CarouselOverrides } from '@constructor-io/constructorio-ui-components';
3
- import { Callbacks, Item } from '../../types';
3
+ import { Callbacks, Item, Translations } from '../../types';
4
4
  interface PiaCustomCarouselProps {
5
5
  items: Array<Item>;
6
6
  componentOverrides?: CarouselOverrides<Item>;
@@ -8,7 +8,8 @@ interface PiaCustomCarouselProps {
8
8
  onResultClick?: (item: Item, position: number, question: string, qnaResultId?: string) => void;
9
9
  question?: string;
10
10
  qnaResultId?: string;
11
+ translations?: Translations;
11
12
  priceCurrency?: string;
12
13
  }
13
- export default function PiaCustomCarousel({ items, componentOverrides, callbacks, onResultClick, question, qnaResultId, priceCurrency, }: PiaCustomCarouselProps): React.JSX.Element | null;
14
+ export default function PiaCustomCarousel({ items, componentOverrides, callbacks, onResultClick, question, qnaResultId, translations, priceCurrency, }: PiaCustomCarouselProps): React.JSX.Element | null;
14
15
  export {};
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
- import { Translations } from '../../types';
2
+ import { ComponentOverrideProps } from '@constructor-io/constructorio-ui-components';
3
+ import { InputRenderProps, Translations } from '../../types';
3
4
  interface InputProps {
4
5
  value?: string;
5
6
  disabled?: boolean;
@@ -8,6 +9,7 @@ interface InputProps {
8
9
  onSubmit: (value: string) => void;
9
10
  onFocus?: () => void;
10
11
  translations?: Translations;
12
+ componentOverride?: ComponentOverrideProps<InputRenderProps>;
11
13
  }
12
- declare function Input({ value: providedValue, placeholder, disabled, onSubmit, onFocus, translations, }: InputProps): React.JSX.Element;
14
+ declare function Input({ value: providedValue, placeholder, disabled, onSubmit, onFocus, translations, componentOverride, }: InputProps): React.JSX.Element;
13
15
  export default Input;
@@ -59,6 +59,7 @@ export type Translations = {
59
59
  'Is this answer useful?'?: string;
60
60
  'Learn More.'?: string;
61
61
  'Ask about this product'?: string;
62
+ 'Add to Cart'?: string;
62
63
  };
63
64
  export type QuestionSource = 'user' | 'suggestion';
64
65
  export interface PiaCallbackContext {
@@ -70,6 +71,12 @@ export interface Callbacks {
70
71
  onQuestionSubmit?: (question: string, context: PiaCallbackContext, source: QuestionSource) => void;
71
72
  /** Called when a product card in the carousel is clicked. */
72
73
  onProductCardClick?: (item: Item) => void;
74
+ /**
75
+ * Called when the "Add to Cart" button on a product card is clicked.
76
+ * Providing this callback is what renders the button.
77
+ * Clicking the button does not fire `onProductCardClick`.
78
+ */
79
+ onAddToCart?: (item: Item, event: React.MouseEvent) => void;
73
80
  /** Called when the user submits positive or negative feedback on an answer. */
74
81
  onFeedback?: (type: FeedbackType) => void;
75
82
  /** Called when a new answer is received. Passes the full conversation history. */
@@ -139,6 +146,16 @@ export interface FeedbackRenderProps {
139
146
  export interface LoadingRenderProps {
140
147
  skeleton: ReactNode;
141
148
  }
149
+ /**
150
+ * Render props passed to a custom Input override.
151
+ */
152
+ export interface InputRenderProps {
153
+ disabled: boolean;
154
+ placeholder: string;
155
+ onSubmit: (value: string) => void;
156
+ onFocus?: () => void;
157
+ translations?: Translations;
158
+ }
142
159
  /**
143
160
  * Component overrides for CioPia.
144
161
  * Allows customization of sub-components via reactNode or render props functions.
@@ -146,6 +163,7 @@ export interface LoadingRenderProps {
146
163
  export interface CioPiaComponentOverrides extends ComponentOverrideProps<CioPiaRenderProps> {
147
164
  carousel?: CarouselOverrides<Item>;
148
165
  answer?: ComponentOverrideProps<AnswerRenderProps>;
166
+ input?: ComponentOverrideProps<InputRenderProps>;
149
167
  suggestedQuestions?: ComponentOverrideProps<SuggestedQuestionsRenderProps>;
150
168
  disclaimer?: ComponentOverrideProps<DisclaimerRenderProps>;
151
169
  feedback?: ComponentOverrideProps<FeedbackRenderProps>;
@@ -1,2 +1,2 @@
1
- declare const _default: "2.10.0";
1
+ declare const _default: "2.10.2";
2
2
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructor-io/constructorio-ui-pia",
3
- "version": "2.10.0",
3
+ "version": "2.10.2",
4
4
  "description": "Constructor.io AI Product Insights Agent UI library for web applications",
5
5
  "author": "Constructor.io Corporation",
6
6
  "license": "MIT",
@@ -26,8 +26,10 @@
26
26
  "module": "lib/mjs/index.js",
27
27
  "exports": {
28
28
  "./styles.css": {
29
+ "style": "./lib/mjs/styles.css",
29
30
  "import": "./lib/mjs/styles.css",
30
- "require": "./lib/cjs/styles.css"
31
+ "require": "./lib/cjs/styles.css",
32
+ "default": "./lib/mjs/styles.css"
31
33
  },
32
34
  "./constructorio-ui-pia-bundled": {
33
35
  "import": "./dist/constructorio-ui-pia-bundled.js",