@constructor-io/constructorio-ui-quizzes 1.0.1

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.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -0
  3. package/lib/cjs/components/BackButton/BackButton.js +12 -0
  4. package/lib/cjs/components/CTAButton/CTAButton.js +10 -0
  5. package/lib/cjs/components/CioQuiz/actions.js +12 -0
  6. package/lib/cjs/components/CioQuiz/context.js +5 -0
  7. package/lib/cjs/components/CioQuiz/index.js +115 -0
  8. package/lib/cjs/components/CioQuiz/reducer.js +32 -0
  9. package/lib/cjs/components/ControlBar/ControlBar.js +13 -0
  10. package/lib/cjs/components/CoverTypeQuestion/CoverTypeQuestion.js +42 -0
  11. package/lib/cjs/components/OpenTextTypeQuestion/OpenTextTypeQuestion.js +66 -0
  12. package/lib/cjs/components/QuestionDescription/QuestionDescription.js +9 -0
  13. package/lib/cjs/components/QuestionTitle/QuestionTitle.js +9 -0
  14. package/lib/cjs/components/QuizQuestions/index.js +17 -0
  15. package/lib/cjs/components/RedoButton/RedoButton.js +12 -0
  16. package/lib/cjs/components/RedoButton/RedoSVG.js +15 -0
  17. package/lib/cjs/components/ResultCard/ResultCard.js +45 -0
  18. package/lib/cjs/components/ResultContainer/ResultContainer.js +39 -0
  19. package/lib/cjs/components/ResultCtaButton/ResultCtaButton.js +16 -0
  20. package/lib/cjs/components/ResultFilters/ResultFilters.js +30 -0
  21. package/lib/cjs/components/Results/Results.js +16 -0
  22. package/lib/cjs/components/SelectTypeQuestion/SelectTypeQuestion.js +90 -0
  23. package/lib/cjs/components/Spinner/Spinner.js +12 -0
  24. package/lib/cjs/components/ZeroResults/ZeroResults.js +13 -0
  25. package/lib/cjs/constants.js +36 -0
  26. package/lib/cjs/hooks/useCioClient.js +11 -0
  27. package/lib/cjs/index.js +5 -0
  28. package/lib/cjs/stories/Quiz/argTypes.js +30 -0
  29. package/lib/cjs/types.js +2 -0
  30. package/lib/cjs/utils.js +97 -0
  31. package/lib/mjs/components/BackButton/BackButton.js +9 -0
  32. package/lib/mjs/components/CTAButton/CTAButton.js +7 -0
  33. package/lib/mjs/components/CioQuiz/actions.js +9 -0
  34. package/lib/mjs/components/CioQuiz/context.js +2 -0
  35. package/lib/mjs/components/CioQuiz/index.js +111 -0
  36. package/lib/mjs/components/CioQuiz/reducer.js +55 -0
  37. package/lib/mjs/components/ControlBar/ControlBar.js +10 -0
  38. package/lib/mjs/components/CoverTypeQuestion/CoverTypeQuestion.js +37 -0
  39. package/lib/mjs/components/OpenTextTypeQuestion/OpenTextTypeQuestion.js +61 -0
  40. package/lib/mjs/components/QuestionDescription/QuestionDescription.js +6 -0
  41. package/lib/mjs/components/QuestionTitle/QuestionTitle.js +6 -0
  42. package/lib/mjs/components/QuizQuestions/index.js +13 -0
  43. package/lib/mjs/components/RedoButton/RedoButton.js +9 -0
  44. package/lib/mjs/components/RedoButton/RedoSVG.js +12 -0
  45. package/lib/mjs/components/ResultCard/ResultCard.js +37 -0
  46. package/lib/mjs/components/ResultContainer/ResultContainer.js +34 -0
  47. package/lib/mjs/components/ResultCtaButton/ResultCtaButton.js +12 -0
  48. package/lib/mjs/components/ResultFilters/ResultFilters.js +27 -0
  49. package/lib/mjs/components/Results/Results.js +9 -0
  50. package/lib/mjs/components/SelectTypeQuestion/SelectTypeQuestion.js +85 -0
  51. package/lib/mjs/components/Spinner/Spinner.js +8 -0
  52. package/lib/mjs/components/ZeroResults/ZeroResults.js +10 -0
  53. package/lib/mjs/constants.js +33 -0
  54. package/lib/mjs/hooks/useCioClient.js +9 -0
  55. package/lib/mjs/index.js +2 -0
  56. package/lib/mjs/stories/Quiz/argTypes.js +27 -0
  57. package/lib/mjs/types.js +1 -0
  58. package/lib/mjs/utils.js +83 -0
  59. package/lib/styles.css +689 -0
  60. package/lib/types/components/BackButton/BackButton.d.ts +3 -0
  61. package/lib/types/components/CTAButton/CTAButton.d.ts +6 -0
  62. package/lib/types/components/CioQuiz/actions.d.ts +25 -0
  63. package/lib/types/components/CioQuiz/context.d.ts +17 -0
  64. package/lib/types/components/CioQuiz/index.d.ts +14 -0
  65. package/lib/types/components/CioQuiz/reducer.d.ts +12 -0
  66. package/lib/types/components/ControlBar/ControlBar.d.ts +10 -0
  67. package/lib/types/components/CoverTypeQuestion/CoverTypeQuestion.d.ts +2 -0
  68. package/lib/types/components/OpenTextTypeQuestion/OpenTextTypeQuestion.d.ts +7 -0
  69. package/lib/types/components/QuestionDescription/QuestionDescription.d.ts +6 -0
  70. package/lib/types/components/QuestionTitle/QuestionTitle.d.ts +6 -0
  71. package/lib/types/components/QuizQuestions/index.d.ts +5 -0
  72. package/lib/types/components/RedoButton/RedoButton.d.ts +6 -0
  73. package/lib/types/components/RedoButton/RedoSVG.d.ts +3 -0
  74. package/lib/types/components/ResultCard/ResultCard.d.ts +11 -0
  75. package/lib/types/components/ResultContainer/ResultContainer.d.ts +7 -0
  76. package/lib/types/components/ResultCtaButton/ResultCtaButton.d.ts +9 -0
  77. package/lib/types/components/ResultFilters/ResultFilters.d.ts +6 -0
  78. package/lib/types/components/Results/Results.d.ts +10 -0
  79. package/lib/types/components/SelectTypeQuestion/SelectTypeQuestion.d.ts +3 -0
  80. package/lib/types/components/Spinner/Spinner.d.ts +2 -0
  81. package/lib/types/components/ZeroResults/ZeroResults.d.ts +6 -0
  82. package/lib/types/constants.d.ts +11 -0
  83. package/lib/types/hooks/useCioClient.d.ts +8 -0
  84. package/lib/types/index.d.ts +2 -0
  85. package/lib/types/stories/Quiz/argTypes.d.ts +30 -0
  86. package/lib/types/types.d.ts +1 -0
  87. package/lib/types/utils.d.ts +31 -0
  88. package/package.json +91 -0
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ function QuestionTitle(props) {
3
+ const { title } = props;
4
+ return React.createElement("h1", { className: 'cio-question-title' }, title);
5
+ }
6
+ export default QuestionTitle;
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import OpenTextQuestion from '../OpenTextTypeQuestion/OpenTextTypeQuestion';
3
+ import CoverTypeQuestion from '../CoverTypeQuestion/CoverTypeQuestion';
4
+ import SelectTypeQuestion from '../SelectTypeQuestion/SelectTypeQuestion';
5
+ import { getQuestionTypes } from '../../utils';
6
+ export default function QuizQuestions(props) {
7
+ const { questionResponse: { next_question: nextQuestion }, } = props;
8
+ const questionTypes = getQuestionTypes(nextQuestion?.type);
9
+ return (React.createElement(React.Fragment, null,
10
+ questionTypes.isOpenQuestion && React.createElement(OpenTextQuestion, { key: nextQuestion?.id }),
11
+ questionTypes.isCoverQuestion && React.createElement(CoverTypeQuestion, { key: nextQuestion?.id }),
12
+ questionTypes.isSelectQuestion && React.createElement(SelectTypeQuestion, { key: nextQuestion?.id })));
13
+ }
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import RedoSVG from './RedoSVG';
3
+ function RedoButton(props) {
4
+ const { redoText = 'Redo Quiz', disabled, ...rest } = props;
5
+ return (React.createElement("button", { type: 'button', className: `${disabled ? 'cio-question-redo-button disabled' : 'cio-question-redo-button'}`, ...rest },
6
+ React.createElement(RedoSVG, null),
7
+ React.createElement("span", null, redoText)));
8
+ }
9
+ export default RedoButton;
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ function RedoSVG() {
3
+ return (React.createElement("svg", { width: '16', height: '16', viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg' },
4
+ React.createElement("g", { clipPath: 'url(#clip0_7495_177230)' },
5
+ React.createElement("path", { d: 'M0.667969 2.66699V6.66699H4.66797', stroke: '#0A0B0C', strokeWidth: '1.33333', strokeLinecap: 'round', strokeLinejoin: 'round' }),
6
+ React.createElement("path", { d: 'M15.332 13.333V9.33301H11.332', stroke: '#0A0B0C', strokeWidth: '1.33333', strokeLinecap: 'round', strokeLinejoin: 'round' }),
7
+ React.createElement("path", { d: 'M13.6613 6.00038C13.3232 5.0449 12.7485 4.19064 11.991 3.51732C11.2334 2.844 10.3177 2.37355 9.32911 2.14988C8.34056 1.92621 7.31147 1.9566 6.33784 2.23823C5.36422 2.51985 4.4778 3.04352 3.7613 3.76038L0.667969 6.66704M15.3346 9.33371L12.2413 12.2404C11.5248 12.9572 10.6384 13.4809 9.66476 13.7625C8.69114 14.0441 7.66204 14.0745 6.67349 13.8509C5.68495 13.6272 4.76917 13.1568 4.01161 12.4834C3.25406 11.8101 2.67941 10.9559 2.3413 10.0004', stroke: '#0A0B0C', strokeWidth: '1.33333', strokeLinecap: 'round', strokeLinejoin: 'round' })),
8
+ React.createElement("defs", null,
9
+ React.createElement("clipPath", { id: 'clip0_7495_177230' },
10
+ React.createElement("rect", { width: '16', height: '16', fill: 'white' })))));
11
+ }
12
+ export default RedoSVG;
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import ResultCtaButton from '../ResultCtaButton/ResultCtaButton';
3
+ export default function ResultCard(props) {
4
+ const { result, addToCartCallback, clickItemCallback, salePriceKey, regularPriceKey } = props;
5
+ const salePrice = salePriceKey && result?.data?.[salePriceKey];
6
+ const regularPrice = regularPriceKey && result?.data?.[regularPriceKey];
7
+ const clickHandler = () => {
8
+ if (clickItemCallback && typeof clickItemCallback === 'function') {
9
+ clickItemCallback(result);
10
+ }
11
+ };
12
+ const keyDownHandler = (event) => {
13
+ if (event?.key === ' ' || event?.key === 'Enter') {
14
+ if (clickItemCallback && typeof clickItemCallback === 'function') {
15
+ clickItemCallback(result);
16
+ }
17
+ }
18
+ };
19
+ const resultCardContent = () => (React.createElement(React.Fragment, null,
20
+ React.createElement("div", { className: 'cio-result-card-image' },
21
+ React.createElement("img", { src: result.data?.image_url, alt: 'product' })),
22
+ React.createElement("div", { className: 'cio-result-card-text' },
23
+ React.createElement("p", { className: 'cio-result-card-title' }, result.value),
24
+ React.createElement("div", { className: 'cio-result-card-prices' },
25
+ salePrice && React.createElement("span", { className: 'cio-result-card-sale-price' },
26
+ "$",
27
+ salePrice),
28
+ regularPrice && (React.createElement("span", { className: `cio-result-card-regular-price${salePrice ? '--strike-through' : ''}` },
29
+ "$",
30
+ regularPrice)))),
31
+ React.createElement(ResultCtaButton, { item: result, callback: addToCartCallback })));
32
+ if (clickItemCallback) {
33
+ return (React.createElement("div", { onClick: clickHandler, onKeyDown: keyDownHandler, className: 'cio-result-card', role: 'button', tabIndex: 0 }, resultCardContent()));
34
+ }
35
+ return (React.createElement("div", { className: 'cio-result-card' },
36
+ React.createElement("a", { className: 'cio-result-card-anchor', href: result.data?.url }, resultCardContent())));
37
+ }
@@ -0,0 +1,34 @@
1
+ import React, { useContext } from 'react';
2
+ import RedoButton from '../RedoButton/RedoButton';
3
+ import { QuestionTypes } from '../CioQuiz/actions';
4
+ import QuizContext from '../CioQuiz/context';
5
+ import ResultFilters from '../ResultFilters/ResultFilters';
6
+ import ZeroResults from '../ZeroResults/ZeroResults';
7
+ import Results from '../Results/Results';
8
+ export default function ResultContainer(props) {
9
+ const { options, resetQuizSessionId } = props;
10
+ const { addToCartCallback, clickItemCallback, resultCardSalePriceKey, resultCardRegularPriceKey, } = options;
11
+ const { resultsResponse } = useContext(QuizContext);
12
+ const { dispatch } = useContext(QuizContext);
13
+ const filterExpression = resultsResponse?.request?.collection_filter_expression;
14
+ const zeroResults = !resultsResponse?.response?.results?.length;
15
+ const resultsTitle = zeroResults ? 'Oops, there are no results' : 'Here are your results';
16
+ const onResetClick = () => {
17
+ if (dispatch && resultsResponse) {
18
+ resetQuizSessionId();
19
+ dispatch({
20
+ type: QuestionTypes.Reset,
21
+ });
22
+ }
23
+ };
24
+ if (resultsResponse) {
25
+ return (React.createElement("div", { className: 'cio-results-container' },
26
+ React.createElement("h1", { className: 'cio-results-title' }, resultsTitle),
27
+ React.createElement("div", { className: 'cio-results-filter-and-redo-container' },
28
+ React.createElement(ResultFilters, { filters: filterExpression }),
29
+ React.createElement(RedoButton, { onClick: onResetClick })),
30
+ !zeroResults && (React.createElement(Results, { addToCartCallback: addToCartCallback, clickItemCallback: clickItemCallback, resultCardSalePriceKey: resultCardSalePriceKey, resultCardRegularPriceKey: resultCardRegularPriceKey })),
31
+ zeroResults && React.createElement(ZeroResults, null)));
32
+ }
33
+ return React.createElement("div", null, "Loading");
34
+ }
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ export default function ResultCtaButton(props) {
3
+ const { item, callback, className } = props;
4
+ const clickHandler = (e) => {
5
+ e.preventDefault();
6
+ if (callback && typeof callback === 'function') {
7
+ e.stopPropagation();
8
+ callback(item);
9
+ }
10
+ };
11
+ return (React.createElement("button", { type: 'button', className: `cio-result-card-cta-button ${className || ''}`, onClick: clickHandler }, "Add to Cart"));
12
+ }
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ function ResultFilters(props) {
3
+ const { filters } = props;
4
+ const isValueExpression = (exp) => 'name' in exp && 'value' in exp;
5
+ const isAndFilter = (exp) => 'and' in exp;
6
+ const isOrFilter = (exp) => 'or' in exp;
7
+ const getFilterValuesFromExpression = (exp) => {
8
+ if (!exp) {
9
+ return [];
10
+ }
11
+ if (isAndFilter(exp)) {
12
+ return exp.and.flatMap((innerExpression) => getFilterValuesFromExpression(innerExpression));
13
+ }
14
+ if (isOrFilter(exp)) {
15
+ return exp.or.flatMap((innerExpression) => getFilterValuesFromExpression(innerExpression));
16
+ }
17
+ if (isValueExpression(exp)) {
18
+ return [exp.value];
19
+ }
20
+ return [];
21
+ };
22
+ const filterValues = [...new Set(getFilterValuesFromExpression(filters))];
23
+ return (React.createElement("div", { className: 'cio-results-filter-container' },
24
+ React.createElement("p", null, "Because you answered"),
25
+ React.createElement("div", { className: 'cio-results-filter-options' }, filterValues?.map((filter) => (React.createElement("div", { className: 'cio-results-filter-option', key: filter }, filter))))));
26
+ }
27
+ export default ResultFilters;
@@ -0,0 +1,9 @@
1
+ import React, { useContext } from 'react';
2
+ import QuizContext from '../CioQuiz/context';
3
+ import ResultCard from '../ResultCard/ResultCard';
4
+ function Results(props) {
5
+ const { addToCartCallback, clickItemCallback, resultCardSalePriceKey, resultCardRegularPriceKey, } = props;
6
+ const { resultsResponse } = useContext(QuizContext);
7
+ return (React.createElement("div", { className: 'cio-results' }, resultsResponse?.response?.results?.map((result) => (React.createElement(ResultCard, { result: result, key: result.data?.id, salePriceKey: resultCardSalePriceKey, regularPriceKey: resultCardRegularPriceKey, addToCartCallback: addToCartCallback, clickItemCallback: clickItemCallback })))));
8
+ }
9
+ export default Results;
@@ -0,0 +1,85 @@
1
+ import React, { useEffect, useState, useContext } from 'react';
2
+ import QuestionTitle from '../QuestionTitle/QuestionTitle';
3
+ import QuestionDescription from '../QuestionDescription/QuestionDescription';
4
+ import QuizContext from '../CioQuiz/context';
5
+ import { renderImages } from '../../utils';
6
+ import { QuestionTypes } from '../CioQuiz/actions';
7
+ import ControlBar from '../ControlBar/ControlBar';
8
+ function SelectTypeQuestion() {
9
+ const { questionResponse, state, quizNextHandler, quizBackHandler, isFirstQuestion } = useContext(QuizContext);
10
+ let question;
11
+ let type;
12
+ let hasImages = false;
13
+ if (questionResponse) {
14
+ question = questionResponse.next_question;
15
+ type = question.type;
16
+ hasImages = questionResponse.next_question.options.some((option) => option.images);
17
+ }
18
+ const [selected, setSelected] = useState({});
19
+ const isDisabled = Object.keys(selected).length === 0;
20
+ useEffect(() => {
21
+ if (questionResponse?.next_question?.type) {
22
+ const answers = state?.answerInputs?.[questionResponse.next_question.id] || [];
23
+ const prevSelected = {};
24
+ answers?.forEach((answer) => {
25
+ prevSelected[Number(answer)] = true;
26
+ });
27
+ setSelected(prevSelected);
28
+ }
29
+ }, [questionResponse, state?.answerInputs]);
30
+ const toggleIdSelected = (id) => {
31
+ if (type === QuestionTypes.SingleSelect) {
32
+ setSelected({ [id]: true });
33
+ }
34
+ else if (type === QuestionTypes.MultipleSelect) {
35
+ if (selected[id]) {
36
+ const newState = { ...selected };
37
+ delete newState[id];
38
+ setSelected(newState);
39
+ }
40
+ else {
41
+ setSelected({ ...selected, [id]: true });
42
+ }
43
+ }
44
+ };
45
+ const onOptionKeyDown = (event, id) => {
46
+ if (event?.key === ' ' || event?.key === 'Enter') {
47
+ toggleIdSelected(id);
48
+ }
49
+ };
50
+ const onNextClick = () => {
51
+ if (quizNextHandler && !isDisabled && questionResponse) {
52
+ const questionType = type === QuestionTypes.SingleSelect
53
+ ? QuestionTypes.SingleSelect
54
+ : QuestionTypes.MultipleSelect;
55
+ quizNextHandler({
56
+ type: questionType,
57
+ payload: {
58
+ questionId: questionResponse?.next_question.id,
59
+ input: Object.keys(selected).filter((key) => selected[Number(key)]),
60
+ isLastQuestion: questionResponse.is_last_question,
61
+ },
62
+ });
63
+ }
64
+ };
65
+ if (question) {
66
+ return (React.createElement("div", { className: 'cio-select-question-container' },
67
+ React.createElement("div", { className: 'cio-select-question-text' },
68
+ React.createElement(QuestionTitle, { title: question.title }),
69
+ question?.description ? React.createElement(QuestionDescription, { description: question.description }) : ''),
70
+ React.createElement("div", { className: `${!hasImages
71
+ ? 'cio-question-options-container-text-only'
72
+ : 'cio-question-options-container'}` }, question?.options?.map((option) => (React.createElement("div", { className: `${!hasImages
73
+ ? 'cio-question-option-container-text-only'
74
+ : 'cio-question-option-container'} ${selected[option.id] ? 'selected' : ''}`, onClick: () => {
75
+ toggleIdSelected(option.id);
76
+ }, onKeyDown: (event) => {
77
+ onOptionKeyDown(event, option.id);
78
+ }, role: 'button', tabIndex: 0, key: option.id },
79
+ option.images ? renderImages(option.images, 'cio-question-option-image') : '',
80
+ React.createElement("div", { className: 'cio-question-option-value' }, option?.value))))),
81
+ React.createElement(ControlBar, { nextButtonHandler: onNextClick, isNextButtonDisabled: isDisabled, backButtonHandler: quizBackHandler, showBackButton: !isFirstQuestion, ctaButtonText: question?.cta_text })));
82
+ }
83
+ return null;
84
+ }
85
+ export default SelectTypeQuestion;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ export default function Spinner() {
3
+ return (React.createElement("div", { className: 'cio-spinner' },
4
+ React.createElement("div", null),
5
+ React.createElement("div", null),
6
+ React.createElement("div", null),
7
+ React.createElement("div", null)));
8
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import CTAButton from '../CTAButton/CTAButton';
3
+ function ZeroResults(props) {
4
+ const { onResetClick } = props;
5
+ return (React.createElement("div", { className: 'cio-zero-results' },
6
+ React.createElement("h3", { className: 'cio-zero-results-subtitle' }, "Sorry, it seems like we couldn\u2019t find results based on your answers."),
7
+ React.createElement("p", { className: 'cio-zero-results-description' }, "This is embarrassing \uD83D\uDE22. It might be that some of the questions are not properly set up from our end. Would you give us another try?"),
8
+ React.createElement(CTAButton, { ctaText: 'Try Again', onClick: onResetClick })));
9
+ }
10
+ export default ZeroResults;
@@ -0,0 +1,33 @@
1
+ // Autocomplete key index
2
+ export const apiKey = 'key_wJSdZSiesX5hiVLt';
3
+ export const quizId = 'coffee-quiz';
4
+ /// //////////////////////////////
5
+ // Storybook Folder Descriptions
6
+ /// //////////////////////////////
7
+ export const componentDescription = `- import \`CioQuiz\` to render in your JSX.
8
+ - This component handles state management, data fetching, and rendering logic.
9
+ - To use this component, \`quizId\`, \`resultsPageOptions\`, and one of \`apiKey\` or \`cioJsClient\` are required.
10
+ - \`resultsPageOptions\` lets you configure the results page
11
+ - \`addToCartCallback\` is a callback function that will be called when the "Add to cart" button is clicked
12
+ - \`clickItemCallback\` is an optional callback function that will be called when the result card is clicked. The default behavior is redirecting the user to the item's URL
13
+ - \`resultCardRegularPriceKey\` is a parameter that specifies the metadata field name for the regular price
14
+ - \`resultCardSalePriceKey\` is an optional parameter that specifies the metadata field name for the sale price
15
+ - Use different props to configure the behavior of this component.
16
+ - The following stories show how different props affect the component's behavior
17
+
18
+ > Note: \`cioJsClient\` refers to an instance of the [constructorio-client-javascript](https://www.npmjs.com/package/@constructor-io/constructorio-client-javascript)
19
+ `;
20
+ /// //////////////////////////////
21
+ // Storybook Stories
22
+ /// //////////////////////////////
23
+ export const basicDescription = `Pass an \`apiKey\` and a \`quizId\` to request questions and quiz results from Constructor's servers`;
24
+ export const cioJsClientDescription = `If you are already using an instance of the \`ConstructorIOClient\`, you can pass a \`cioJsClient\` instead of an \`apiKey\` to request results from Constructor's servers
25
+
26
+ > Note: \`cioJsClient\` refers to an instance of the [constructorio-client-javascript](https://www.npmjs.com/package/@constructor-io/constructorio-client-javascript)`;
27
+ export var RequestStates;
28
+ (function (RequestStates) {
29
+ RequestStates[RequestStates["Stale"] = 0] = "Stale";
30
+ RequestStates[RequestStates["Loading"] = 1] = "Loading";
31
+ RequestStates[RequestStates["Success"] = 2] = "Success";
32
+ RequestStates[RequestStates["Error"] = 3] = "Error";
33
+ })(RequestStates || (RequestStates = {}));
@@ -0,0 +1,9 @@
1
+ import { useMemo } from 'react';
2
+ import { getCioClient } from '../utils';
3
+ const useCioClient = ({ apiKey, cioJsClient }) => {
4
+ if (!apiKey && !cioJsClient) {
5
+ console.error('Either apiKey or cioJsClient is required');
6
+ }
7
+ return useMemo(() => cioJsClient || getCioClient(apiKey), [apiKey, cioJsClient]);
8
+ };
9
+ export default useCioClient;
@@ -0,0 +1,2 @@
1
+ import CioQuiz from './components/CioQuiz';
2
+ export default CioQuiz;
@@ -0,0 +1,27 @@
1
+ // eslint-disable-next-line
2
+ export const argTypes = {
3
+ apiKey: {
4
+ type: { name: 'string' },
5
+ description: 'Your Constructor API key',
6
+ table: {
7
+ type: {
8
+ summary: 'string',
9
+ },
10
+ },
11
+ control: {
12
+ type: 'text',
13
+ },
14
+ },
15
+ quizId: {
16
+ type: { name: 'string' },
17
+ description: 'ID of the quiz',
18
+ table: {
19
+ type: {
20
+ summary: 'string',
21
+ },
22
+ },
23
+ control: {
24
+ type: 'text',
25
+ },
26
+ },
27
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,83 @@
1
+ import React from 'react';
2
+ import ConstructorIOClient from '@constructor-io/constructorio-client-javascript';
3
+ import { QuestionTypes } from './components/CioQuiz/actions';
4
+ export const renderImages = (images, cssClasses) => {
5
+ const { primary_url: primaryUrl, primary_alt: primaryAlt, secondary_url: secondaryUrl, secondary_alt: secondaryAlt, } = images;
6
+ if (primaryUrl) {
7
+ const windowWidth = window.innerWidth;
8
+ let src = primaryUrl;
9
+ let alt = primaryAlt || 'Quiz Image';
10
+ if (windowWidth > 768 && secondaryUrl) {
11
+ src = secondaryUrl;
12
+ if (secondaryAlt) {
13
+ alt = secondaryAlt;
14
+ }
15
+ }
16
+ return (React.createElement("span", { className: `${cssClasses || ''}`.trim() },
17
+ React.createElement("img", { className: 'cio-question-image', src: src, alt: alt })));
18
+ }
19
+ return '';
20
+ };
21
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
22
+ export const getStoryParams = (storyCode, templateCode, importCode) => {
23
+ const code = `
24
+ ${importCode}
25
+ ${storyCode}
26
+ ${templateCode}
27
+ `;
28
+ return {
29
+ docs: {
30
+ source: {
31
+ code,
32
+ language: 'jsx',
33
+ format: true,
34
+ type: 'code',
35
+ },
36
+ },
37
+ };
38
+ };
39
+ export const defaultAddToCartCallbackCode = `"addToCartCallback": (item) => console.dir(item)`;
40
+ export const stringifyWithDefaults = (obj) => {
41
+ const { addToCartCallback, cioJsClient, ...rest } = obj;
42
+ let res = JSON.stringify(rest, null, ' ');
43
+ if (cioJsClient) {
44
+ res = res.replace('"resultsPageOptions": {', `"cioJsClient": cioJsClient,
45
+ "resultsPageOptions": {`);
46
+ }
47
+ res = res.replace('"resultsPageOptions": {', `"resultsPageOptions": {
48
+ ${defaultAddToCartCallbackCode},`);
49
+ return res;
50
+ };
51
+ export const stringify = (obj) => JSON.stringify(obj, null, ' ');
52
+ export const getNextQuestion = (cioClient, quizId, parameters) => cioClient?.quizzes.getQuizNextQuestion(quizId, parameters);
53
+ export const getQuizResults = async (cioClient, quizId, parameters) => cioClient?.quizzes.getQuizResults(quizId, parameters);
54
+ export const getQuestionTypes = (questionType) => {
55
+ const isOpenQuestion = questionType === QuestionTypes.OpenText;
56
+ const isCoverQuestion = questionType === QuestionTypes.Cover;
57
+ const isSingleQuestion = questionType === QuestionTypes.SingleSelect;
58
+ const isMultipleQuestion = questionType === QuestionTypes.MultipleSelect;
59
+ const isSelectQuestion = isSingleQuestion || isMultipleQuestion;
60
+ return {
61
+ isOpenQuestion,
62
+ isCoverQuestion,
63
+ isSingleQuestion,
64
+ isMultipleQuestion,
65
+ isSelectQuestion,
66
+ };
67
+ };
68
+ export const getCioClient = (apiKey) => {
69
+ if (apiKey) {
70
+ return new ConstructorIOClient({
71
+ apiKey,
72
+ });
73
+ }
74
+ return null;
75
+ };
76
+ export function getPreferredColorScheme() {
77
+ let colorScheme = 'light';
78
+ // Check if the dark-mode Media-Query matches
79
+ if (window.matchMedia('(prefers-color-scheme: dark)')?.matches) {
80
+ colorScheme = 'dark';
81
+ }
82
+ return colorScheme;
83
+ }