@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.
- package/README.md +1 -0
- package/dist/constructorio-ui-pia-bundled.js +48 -48
- package/lib/cjs/components/CioPia/CioPia.js +1 -1
- package/lib/cjs/components/CioPia/PiaCustomCarousel.js +18 -2
- package/lib/cjs/components/ConversationHistory/ConversationHistory.js +1 -1
- package/lib/cjs/components/Input/Input.js +20 -12
- package/lib/cjs/components/PiaConversation/PiaConversation.js +1 -1
- package/lib/cjs/components/PiaConversation/PiaModal.js +1 -1
- package/lib/cjs/components/PiaInlineAnswer/PiaInlineAnswer.js +1 -1
- package/lib/cjs/hooks/mocks/MockConstructorIOClient.js +8 -3
- package/lib/cjs/utils/translate.js +1 -0
- package/lib/cjs/version.js +1 -1
- package/lib/mjs/components/CioPia/CioPia.js +1 -1
- package/lib/mjs/components/CioPia/PiaCustomCarousel.js +27 -3
- package/lib/mjs/components/ConversationHistory/ConversationHistory.js +1 -1
- package/lib/mjs/components/Input/Input.js +21 -13
- package/lib/mjs/components/PiaConversation/PiaConversation.js +1 -1
- package/lib/mjs/components/PiaConversation/PiaModal.js +1 -1
- package/lib/mjs/components/PiaInlineAnswer/PiaInlineAnswer.js +1 -1
- package/lib/mjs/hooks/mocks/MockConstructorIOClient.js +9 -9
- package/lib/mjs/utils/translate.js +1 -0
- package/lib/mjs/version.js +1 -1
- package/lib/types/components/CioPia/PiaCustomCarousel.d.ts +3 -2
- package/lib/types/components/Input/Input.d.ts +4 -2
- package/lib/types/types.d.ts +18 -0
- package/lib/types/version.d.ts +1 -1
- package/package.json +4 -2
|
@@ -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)(() =>
|
|
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 (
|
|
20
|
-
onSubmit(
|
|
21
|
-
if (
|
|
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(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
10
|
-
const
|
|
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(
|
|
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.
|
package/lib/cjs/version.js
CHANGED
|
@@ -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(() =>
|
|
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 (
|
|
17
|
-
onSubmit(
|
|
18
|
-
if (
|
|
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(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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(
|
|
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.
|
package/lib/mjs/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '2.10.
|
|
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 {
|
|
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;
|
package/lib/types/types.d.ts
CHANGED
|
@@ -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>;
|
package/lib/types/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: "2.10.
|
|
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.
|
|
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",
|