@constructor-io/constructorio-ui-quizzes 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +1 -1
  2. package/dist/constructorio-ui-quizzes-bundled.js +17 -17
  3. package/lib/cjs/components/CioQuiz/actions.js +11 -1
  4. package/lib/cjs/components/CioQuiz/index.js +15 -103
  5. package/lib/cjs/components/CioQuiz/quizApiReducer.js +32 -0
  6. package/lib/cjs/components/CioQuiz/{reducer.js → quizLocalReducer.js} +2 -2
  7. package/lib/cjs/components/CoverTypeQuestion/CoverTypeQuestion.js +6 -12
  8. package/lib/cjs/components/OpenTextTypeQuestion/OpenTextTypeQuestion.js +12 -18
  9. package/lib/cjs/components/QuizQuestions/index.js +6 -3
  10. package/lib/cjs/components/ResultCard/ResultCard.js +10 -30
  11. package/lib/cjs/components/ResultContainer/ResultContainer.js +8 -31
  12. package/lib/cjs/components/ResultCtaButton/ResultCtaButton.js +7 -26
  13. package/lib/cjs/components/ResultFilters/ResultFilters.js +6 -23
  14. package/lib/cjs/components/Results/Results.js +5 -5
  15. package/lib/cjs/components/SelectTypeQuestion/SelectTypeQuestion.js +16 -23
  16. package/lib/cjs/components/ZeroResults/ZeroResults.js +2 -2
  17. package/lib/cjs/constants.js +3 -2
  18. package/lib/cjs/hooks/useCioClient.js +4 -3
  19. package/lib/cjs/hooks/useConsoleErrors.js +20 -0
  20. package/lib/cjs/hooks/useQuiz.js +48 -0
  21. package/lib/cjs/hooks/useQuizApiState.js +91 -0
  22. package/lib/cjs/hooks/useQuizEvents/index.js +36 -0
  23. package/lib/cjs/hooks/useQuizEvents/useQuizAddToCart.js +20 -0
  24. package/lib/cjs/hooks/useQuizEvents/useQuizBackClick.js +13 -0
  25. package/lib/cjs/hooks/useQuizEvents/useQuizNextClick.js +48 -0
  26. package/lib/cjs/hooks/useQuizEvents/useQuizResultClick.js +19 -0
  27. package/lib/cjs/hooks/useQuizEvents/useQuizResultsLoaded.js +22 -0
  28. package/lib/cjs/hooks/useQuizLocalState.js +20 -0
  29. package/lib/cjs/services/index.js +72 -0
  30. package/lib/cjs/utils.js +42 -21
  31. package/lib/mjs/components/CioQuiz/actions.js +10 -0
  32. package/lib/mjs/components/CioQuiz/index.js +15 -103
  33. package/lib/mjs/components/CioQuiz/quizApiReducer.js +49 -0
  34. package/lib/mjs/components/CioQuiz/{reducer.js → quizLocalReducer.js} +1 -1
  35. package/lib/mjs/components/CoverTypeQuestion/CoverTypeQuestion.js +6 -12
  36. package/lib/mjs/components/OpenTextTypeQuestion/OpenTextTypeQuestion.js +11 -17
  37. package/lib/mjs/components/QuizQuestions/index.js +5 -3
  38. package/lib/mjs/components/ResultCard/ResultCard.js +10 -29
  39. package/lib/mjs/components/ResultContainer/ResultContainer.js +8 -31
  40. package/lib/mjs/components/ResultCtaButton/ResultCtaButton.js +7 -25
  41. package/lib/mjs/components/ResultFilters/ResultFilters.js +5 -23
  42. package/lib/mjs/components/Results/Results.js +3 -3
  43. package/lib/mjs/components/SelectTypeQuestion/SelectTypeQuestion.js +13 -20
  44. package/lib/mjs/components/ZeroResults/ZeroResults.js +2 -2
  45. package/lib/mjs/constants.js +3 -2
  46. package/lib/mjs/hooks/useCioClient.js +4 -3
  47. package/lib/mjs/hooks/useConsoleErrors.js +18 -0
  48. package/lib/mjs/hooks/useQuiz.js +47 -0
  49. package/lib/mjs/hooks/useQuizApiState.js +87 -0
  50. package/lib/mjs/hooks/useQuizEvents/index.js +33 -0
  51. package/lib/mjs/hooks/useQuizEvents/useQuizAddToCart.js +18 -0
  52. package/lib/mjs/hooks/useQuizEvents/useQuizBackClick.js +11 -0
  53. package/lib/mjs/hooks/useQuizEvents/useQuizNextClick.js +45 -0
  54. package/lib/mjs/hooks/useQuizEvents/useQuizResultClick.js +17 -0
  55. package/lib/mjs/hooks/useQuizEvents/useQuizResultsLoaded.js +19 -0
  56. package/lib/mjs/hooks/useQuizLocalState.js +17 -0
  57. package/lib/mjs/services/index.js +60 -0
  58. package/lib/mjs/utils.js +39 -17
  59. package/lib/types/components/CioQuiz/actions.d.ts +20 -0
  60. package/lib/types/components/CioQuiz/context.d.ts +10 -14
  61. package/lib/types/components/CioQuiz/index.d.ts +1 -12
  62. package/lib/types/components/CioQuiz/quizApiReducer.d.ts +14 -0
  63. package/lib/types/components/CioQuiz/{reducer.d.ts → quizLocalReducer.d.ts} +3 -3
  64. package/lib/types/components/QuizQuestions/index.d.ts +1 -4
  65. package/lib/types/components/ResultCard/ResultCard.d.ts +2 -4
  66. package/lib/types/components/ResultContainer/ResultContainer.d.ts +1 -2
  67. package/lib/types/components/ResultCtaButton/ResultCtaButton.d.ts +2 -3
  68. package/lib/types/components/ResultFilters/ResultFilters.d.ts +2 -5
  69. package/lib/types/components/Results/Results.d.ts +1 -7
  70. package/lib/types/components/ZeroResults/ZeroResults.d.ts +1 -1
  71. package/lib/types/constants.d.ts +1 -1
  72. package/lib/types/hooks/useCioClient.d.ts +1 -1
  73. package/lib/types/hooks/useConsoleErrors.d.ts +3 -0
  74. package/lib/types/hooks/useQuiz.d.ts +3 -0
  75. package/lib/types/hooks/useQuizApiState.d.ts +10 -0
  76. package/lib/types/hooks/useQuizEvents/index.d.ts +15 -0
  77. package/lib/types/hooks/useQuizEvents/useQuizAddToCart.d.ts +5 -0
  78. package/lib/types/hooks/useQuizEvents/useQuizBackClick.d.ts +4 -0
  79. package/lib/types/hooks/useQuizEvents/useQuizNextClick.d.ts +5 -0
  80. package/lib/types/hooks/useQuizEvents/useQuizResultClick.d.ts +5 -0
  81. package/lib/types/hooks/useQuizEvents/useQuizResultsLoaded.d.ts +5 -0
  82. package/lib/types/hooks/useQuizLocalState.d.ts +6 -0
  83. package/lib/types/services/index.d.ts +8 -0
  84. package/lib/types/types.d.ts +63 -0
  85. package/lib/types/utils.d.ts +12 -7
  86. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ // Local Actions
1
2
  export var QuestionTypes;
2
3
  (function (QuestionTypes) {
3
4
  QuestionTypes["OpenText"] = "open";
@@ -7,3 +8,12 @@ export var QuestionTypes;
7
8
  QuestionTypes["Back"] = "back";
8
9
  QuestionTypes["Reset"] = "reset";
9
10
  })(QuestionTypes || (QuestionTypes = {}));
11
+ // API actions
12
+ export var QuizAPIActionTypes;
13
+ (function (QuizAPIActionTypes) {
14
+ QuizAPIActionTypes[QuizAPIActionTypes["SET_IS_LOADING"] = 0] = "SET_IS_LOADING";
15
+ QuizAPIActionTypes[QuizAPIActionTypes["SET_IS_ERROR"] = 1] = "SET_IS_ERROR";
16
+ QuizAPIActionTypes[QuizAPIActionTypes["SET_QUIZ_RESULTS"] = 2] = "SET_QUIZ_RESULTS";
17
+ QuizAPIActionTypes[QuizAPIActionTypes["SET_CURRENT_QUESTION"] = 3] = "SET_CURRENT_QUESTION";
18
+ QuizAPIActionTypes[QuizAPIActionTypes["RESET_QUIZ"] = 4] = "RESET_QUIZ";
19
+ })(QuizAPIActionTypes || (QuizAPIActionTypes = {}));
@@ -1,120 +1,32 @@
1
- import React, { useReducer, useState, useEffect, useCallback } from 'react';
1
+ import React from 'react';
2
2
  import QuizContext from './context';
3
- import reducer, { initialState } from './reducer';
4
- import { QuestionTypes } from './actions';
5
3
  import QuizQuestions from '../QuizQuestions';
6
4
  import ResultContainer from '../ResultContainer/ResultContainer';
7
5
  import { RequestStates } from '../../constants';
8
- import { getNextQuestion, getQuizResults } from '../../utils';
9
6
  import Spinner from '../Spinner/Spinner';
10
- import useCioClient from '../../hooks/useCioClient';
7
+ import useQuiz from '../../hooks/useQuiz';
11
8
  export default function CioQuiz(props) {
12
- const { quizId, apiKey, cioJsClient, resultsPageOptions, quizVersionId: quizVersionIdProp, } = props;
13
- if (!quizId) {
14
- // eslint-disable-next-line no-console
15
- console.error('quizId is a required field of type string');
16
- }
17
- if (!resultsPageOptions || Object.keys(resultsPageOptions).length === 0) {
18
- // eslint-disable-next-line no-console
19
- console.error('resultsPageOptions is a required field of type object');
20
- }
21
- if (resultsPageOptions && !resultsPageOptions?.addToCartCallback) {
22
- // eslint-disable-next-line no-console
23
- console.error('resultsPageOptions.addToCartCallback is a required field of type function');
24
- }
25
- const cioClient = useCioClient({ apiKey, cioJsClient });
26
- const [state, dispatch] = useReducer(reducer, initialState);
27
- const [requestState, setRequestState] = useState(RequestStates.Stale);
28
- const [questionResponse, setQuestionResponse] = useState();
29
- const [resultsResponse, setResultsResponse] = useState();
30
- const [firstQuestion, setFirstQuestion] = useState();
31
- const [quizVersionId, setQuizVersionId] = useState(quizVersionIdProp || '');
32
- const [quizSessionId, setQuizSessionId] = useState('');
33
- const isFirstQuestion = firstQuestion?.next_question.id === questionResponse?.next_question.id;
34
- const quizNextHandler = useCallback((action) => {
35
- if (action) {
36
- dispatch(action);
37
- }
38
- }, [dispatch]);
39
- const quizBackHandler = useCallback(() => {
40
- if (dispatch) {
41
- dispatch({ type: QuestionTypes.Back });
42
- }
43
- }, [dispatch]);
9
+ const { cioClient, state, events: { nextQuestion, previousQuestion, resetQuiz, addToCart, resultClick }, } = useQuiz(props);
10
+ const { resultsPageOptions } = props;
44
11
  const contextValue = {
45
- dispatch,
46
- questionResponse,
47
- state,
48
- resultsResponse,
49
- isFirstQuestion,
50
- quizNextHandler,
51
- quizBackHandler,
52
12
  cioClient,
13
+ state,
14
+ nextQuestion,
15
+ previousQuestion,
16
+ resetQuiz,
17
+ addToCart,
18
+ resultClick,
19
+ customClickItemCallback: !!resultsPageOptions?.onQuizResultClick,
53
20
  };
54
- useEffect(() => {
55
- (async () => {
56
- if (cioClient) {
57
- setRequestState(RequestStates.Loading);
58
- if (state.isLastAnswer) {
59
- try {
60
- const quizResults = await getQuizResults(cioClient, quizId, {
61
- answers: state.answers,
62
- resultsPerPage: resultsPageOptions?.numResultsToDisplay,
63
- quizVersionId,
64
- quizSessionId,
65
- });
66
- setResultsResponse(quizResults);
67
- setRequestState(RequestStates.Success);
68
- setQuestionResponse(undefined);
69
- }
70
- catch (error) {
71
- setResultsResponse(undefined);
72
- setRequestState(RequestStates.Error);
73
- }
74
- }
75
- else {
76
- try {
77
- const questionResult = await getNextQuestion(cioClient, quizId, {
78
- answers: state.answers,
79
- quizVersionId,
80
- quizSessionId,
81
- });
82
- setQuestionResponse(questionResult);
83
- setRequestState(RequestStates.Success);
84
- setResultsResponse(undefined);
85
- if (!quizVersionId && questionResult?.quiz_version_id) {
86
- setQuizVersionId(questionResult.quiz_version_id);
87
- }
88
- if (!quizSessionId && questionResult?.quiz_session_id) {
89
- setQuizSessionId(questionResult.quiz_session_id);
90
- }
91
- }
92
- catch (error) {
93
- setRequestState(RequestStates.Error);
94
- }
95
- }
96
- }
97
- })();
98
- // eslint-disable-next-line react-hooks/exhaustive-deps
99
- }, [cioClient, state, quizId, state.isLastAnswer, resultsPageOptions?.numResultsToDisplay]);
100
- useEffect(() => {
101
- if (!firstQuestion) {
102
- setFirstQuestion(questionResponse);
103
- }
104
- // eslint-disable-next-line react-hooks/exhaustive-deps
105
- }, [questionResponse]);
106
- const resetQuizSessionId = () => {
107
- setQuizSessionId('');
108
- };
109
- if (requestState === RequestStates.Loading) {
21
+ if (state.quiz.requestState === RequestStates.Loading) {
110
22
  return (React.createElement("div", { className: 'cio-quiz' },
111
23
  React.createElement(Spinner, null)));
112
24
  }
113
- if (requestState === RequestStates.Success) {
25
+ if (state.quiz.requestState === RequestStates.Success) {
114
26
  return (React.createElement("div", { className: 'cio-quiz' },
115
27
  React.createElement(QuizContext.Provider, { value: contextValue },
116
- resultsResponse && (React.createElement(ResultContainer, { options: resultsPageOptions, resetQuizSessionId: resetQuizSessionId })),
117
- questionResponse && React.createElement(QuizQuestions, { questionResponse: questionResponse }))));
28
+ state.quiz.results && React.createElement(ResultContainer, { options: resultsPageOptions }),
29
+ state.quiz.currentQuestion && React.createElement(QuizQuestions, null))));
118
30
  }
119
31
  return null;
120
32
  }
@@ -0,0 +1,49 @@
1
+ import { RequestStates } from '../../constants';
2
+ import { getFilterValuesFromExpression } from '../../utils';
3
+ import { QuizAPIActionTypes } from './actions';
4
+ export const initialState = {
5
+ quizRequestState: RequestStates.Stale,
6
+ };
7
+ export default function apiReducer(state, action) {
8
+ switch (action.type) {
9
+ case QuizAPIActionTypes.SET_IS_LOADING:
10
+ return {
11
+ ...state,
12
+ quizRequestState: RequestStates.Loading,
13
+ };
14
+ case QuizAPIActionTypes.SET_IS_ERROR:
15
+ return {
16
+ ...state,
17
+ quizRequestState: RequestStates.Error,
18
+ quizResults: undefined,
19
+ };
20
+ case QuizAPIActionTypes.SET_CURRENT_QUESTION:
21
+ return {
22
+ ...state,
23
+ quizRequestState: RequestStates.Success,
24
+ quizVersionId: action.payload?.quizVersionId,
25
+ quizSessionId: action.payload?.quizSessionId,
26
+ quizCurrentQuestion: action.payload?.quizCurrentQuestion,
27
+ quizResults: undefined,
28
+ // If no current question set first question value
29
+ ...(!state.quizCurrentQuestion && {
30
+ quizFirstQuestion: action.payload?.quizCurrentQuestion,
31
+ }),
32
+ };
33
+ case QuizAPIActionTypes.SET_QUIZ_RESULTS: {
34
+ const filterExpression = action.payload?.quizResults?.request?.collection_filter_expression || null;
35
+ const quizResultsFilters = [...new Set(getFilterValuesFromExpression(filterExpression))];
36
+ return {
37
+ ...state,
38
+ quizRequestState: RequestStates.Success,
39
+ quizResults: action.payload?.quizResults,
40
+ quizResultsFilters,
41
+ quizCurrentQuestion: undefined,
42
+ };
43
+ }
44
+ case QuizAPIActionTypes.RESET_QUIZ:
45
+ return initialState;
46
+ default:
47
+ return state;
48
+ }
49
+ }
@@ -10,7 +10,7 @@ function answerInputReducer(state, action) {
10
10
  [String(action.payload.questionId)]: action.payload.input,
11
11
  };
12
12
  }
13
- export default function reducer(state, action) {
13
+ export default function quizLocalReducer(state, action) {
14
14
  switch (action.type) {
15
15
  case QuestionTypes.OpenText:
16
16
  return {
@@ -3,23 +3,17 @@ import QuestionTitle from '../QuestionTitle/QuestionTitle';
3
3
  import QuizContext from '../CioQuiz/context';
4
4
  import QuestionDescription from '../QuestionDescription/QuestionDescription';
5
5
  import { renderImages } from '../../utils';
6
- import { QuestionTypes } from '../CioQuiz/actions';
7
6
  import ControlBar from '../ControlBar/ControlBar';
8
7
  export default function CoverTypeQuestion() {
9
- const { questionResponse, quizBackHandler, quizNextHandler, isFirstQuestion } = useContext(QuizContext);
8
+ const { state, previousQuestion, nextQuestion } = useContext(QuizContext);
10
9
  let question;
11
- if (questionResponse) {
12
- question = questionResponse.next_question;
10
+ if (state?.quiz.currentQuestion) {
11
+ question = state?.quiz.currentQuestion.next_question;
13
12
  }
14
13
  const hasImage = question?.images?.primary_url;
15
14
  const onNextClick = () => {
16
- if (quizNextHandler) {
17
- quizNextHandler({
18
- type: QuestionTypes.Cover,
19
- payload: {
20
- isLastQuestion: questionResponse?.is_last_question,
21
- },
22
- });
15
+ if (nextQuestion) {
16
+ nextQuestion();
23
17
  }
24
18
  };
25
19
  if (question) {
@@ -30,7 +24,7 @@ export default function CoverTypeQuestion() {
30
24
  React.createElement("div", { className: 'cio-question-content' },
31
25
  React.createElement(QuestionTitle, { title: question?.title }),
32
26
  React.createElement(QuestionDescription, { description: question.description }),
33
- React.createElement(ControlBar, { nextButtonHandler: onNextClick, backButtonHandler: quizBackHandler, showBackButton: !isFirstQuestion, ctaButtonText: question?.cta_text })),
27
+ React.createElement(ControlBar, { nextButtonHandler: onNextClick, backButtonHandler: previousQuestion, showBackButton: !state?.quiz.isFirstQuestion, ctaButtonText: question?.cta_text })),
34
28
  hasImage ? renderImages(question.images, 'cio-question-image-container') : ''));
35
29
  }
36
30
  return null;
@@ -3,15 +3,14 @@ import QuestionTitle from '../QuestionTitle/QuestionTitle';
3
3
  import QuestionDescription from '../QuestionDescription/QuestionDescription';
4
4
  import { renderImages } from '../../utils';
5
5
  import QuizContext from '../CioQuiz/context';
6
- import { QuestionTypes } from '../CioQuiz/actions';
7
6
  import ControlBar from '../ControlBar/ControlBar';
8
7
  function OpenTextQuestion(props) {
9
8
  const { initialValue = '', onChangeHandler: userDefinedHandler = null } = props;
10
- const { questionResponse, quizBackHandler, quizNextHandler, isFirstQuestion, state } = useContext(QuizContext);
9
+ const { state, previousQuestion, nextQuestion } = useContext(QuizContext);
11
10
  const [openTextInput, setOpenTextInput] = useState(initialValue);
12
11
  let question;
13
- if (questionResponse) {
14
- question = questionResponse.next_question;
12
+ if (state?.quiz.currentQuestion) {
13
+ question = state?.quiz.currentQuestion.next_question;
15
14
  }
16
15
  const onChangeHandler = (e) => {
17
16
  setOpenTextInput(e.target.value);
@@ -20,15 +19,8 @@ function OpenTextQuestion(props) {
20
19
  }
21
20
  };
22
21
  const onNextClick = () => {
23
- if (quizNextHandler && openTextInput && questionResponse) {
24
- quizNextHandler({
25
- type: QuestionTypes.OpenText,
26
- payload: {
27
- questionId: questionResponse.next_question.id,
28
- input: openTextInput,
29
- isLastQuestion: questionResponse.is_last_question,
30
- },
31
- });
22
+ if (nextQuestion && openTextInput) {
23
+ nextQuestion(openTextInput);
32
24
  }
33
25
  };
34
26
  const onKeyDownHandler = (e) => {
@@ -38,11 +30,13 @@ function OpenTextQuestion(props) {
38
30
  }
39
31
  };
40
32
  useEffect(() => {
41
- if (questionResponse) {
42
- const openTextAnswer = state?.answerInputs?.[questionResponse?.next_question.id] || initialValue;
33
+ if (state?.quiz.currentQuestion) {
34
+ const questionId = state?.quiz.currentQuestion?.next_question.id;
35
+ const currentAnswer = state.answers.inputs?.[questionId];
36
+ const openTextAnswer = currentAnswer || initialValue;
43
37
  setOpenTextInput(openTextAnswer);
44
38
  }
45
- }, [questionResponse, state, initialValue]);
39
+ }, [state, initialValue]);
46
40
  if (question) {
47
41
  const hasImage = question?.images?.primary_url;
48
42
  return (React.createElement("div", { className: `
@@ -54,7 +48,7 @@ function OpenTextQuestion(props) {
54
48
  React.createElement(QuestionTitle, { title: question.title }),
55
49
  React.createElement(QuestionDescription, { description: question.description }),
56
50
  React.createElement("input", { className: 'cio-question-input-text', placeholder: question.input_placeholder || 'Answer here...', value: openTextInput, onChange: onChangeHandler, onKeyDown: onKeyDownHandler }),
57
- React.createElement(ControlBar, { nextButtonHandler: onNextClick, isNextButtonDisabled: !openTextInput, backButtonHandler: quizBackHandler, showBackButton: !isFirstQuestion, ctaButtonText: question?.cta_text }))));
51
+ React.createElement(ControlBar, { nextButtonHandler: onNextClick, isNextButtonDisabled: !openTextInput, backButtonHandler: previousQuestion, showBackButton: !state?.quiz.isFirstQuestion, ctaButtonText: question?.cta_text }))));
58
52
  }
59
53
  return null;
60
54
  }
@@ -1,10 +1,12 @@
1
- import React from 'react';
1
+ import React, { useContext } from 'react';
2
2
  import OpenTextQuestion from '../OpenTextTypeQuestion/OpenTextTypeQuestion';
3
3
  import CoverTypeQuestion from '../CoverTypeQuestion/CoverTypeQuestion';
4
4
  import SelectTypeQuestion from '../SelectTypeQuestion/SelectTypeQuestion';
5
5
  import { getQuestionTypes } from '../../utils';
6
- export default function QuizQuestions(props) {
7
- const { questionResponse: { next_question: nextQuestion }, } = props;
6
+ import QuizContext from '../CioQuiz/context';
7
+ export default function QuizQuestions() {
8
+ const { state } = useContext(QuizContext);
9
+ const nextQuestion = state?.quiz.currentQuestion?.next_question;
8
10
  const questionTypes = getQuestionTypes(nextQuestion?.type);
9
11
  return (React.createElement(React.Fragment, null,
10
12
  questionTypes.isOpenQuestion && React.createElement(OpenTextQuestion, { key: nextQuestion?.id }),
@@ -2,39 +2,20 @@ import React, { useContext } from 'react';
2
2
  import ResultCtaButton from '../ResultCtaButton/ResultCtaButton';
3
3
  import QuizContext from '../CioQuiz/context';
4
4
  export default function ResultCard(props) {
5
- const { result, addToCartCallback, clickItemCallback: customClickItemCallback, salePriceKey, regularPriceKey, resultPosition, } = props;
6
- const { resultsResponse, cioClient } = useContext(QuizContext);
5
+ const { result, salePriceKey, regularPriceKey, resultPosition } = props;
6
+ const { resultClick, customClickItemCallback } = useContext(QuizContext);
7
7
  const salePrice = salePriceKey && result?.data?.[salePriceKey];
8
8
  const regularPrice = regularPriceKey && result?.data?.[regularPriceKey];
9
- const clickItemCallback = () => {
10
- /* eslint-disable @typescript-eslint/naming-convention */
11
- if (resultsResponse && resultsResponse.request && resultsResponse.response) {
12
- const { quiz_id, quiz_session_id, quiz_version_id, result_id, request: { section, page, num_results_per_page }, response: { total_num_results }, } = resultsResponse;
13
- /* eslint-enable @typescript-eslint/naming-convention */
14
- cioClient?.tracker.trackQuizResultClick({
15
- quiz_id,
16
- quiz_version_id,
17
- quiz_session_id,
18
- item_id: result.data?.id,
19
- item_name: result?.value,
20
- section,
21
- result_count: total_num_results,
22
- result_page: page,
23
- result_id,
24
- result_position_on_page: resultPosition,
25
- num_results_per_page,
26
- });
27
- }
28
- if (customClickItemCallback && typeof customClickItemCallback === 'function') {
29
- customClickItemCallback(result);
30
- }
31
- };
32
9
  const clickHandler = () => {
33
- clickItemCallback();
10
+ if (resultClick) {
11
+ resultClick(result, resultPosition);
12
+ }
34
13
  };
35
14
  const keyDownHandler = (event) => {
36
15
  if (event?.key === ' ' || event?.key === 'Enter') {
37
- clickItemCallback();
16
+ if (resultClick) {
17
+ resultClick(result, resultPosition);
18
+ }
38
19
  }
39
20
  };
40
21
  const resultCardContent = () => (React.createElement(React.Fragment, null,
@@ -49,7 +30,7 @@ export default function ResultCard(props) {
49
30
  regularPrice && (React.createElement("span", { className: `cio-result-card-regular-price${salePrice ? '--strike-through' : ''}` },
50
31
  "$",
51
32
  regularPrice)))),
52
- React.createElement(ResultCtaButton, { item: result, callback: addToCartCallback, price: salePrice || regularPrice })));
33
+ React.createElement(ResultCtaButton, { item: result, price: salePrice || regularPrice })));
53
34
  const resultCardContentWithLink = () => (React.createElement("a", { className: 'cio-result-card-anchor', href: result.data?.url }, resultCardContent()));
54
- return (React.createElement("div", { onClick: clickHandler, onKeyDown: keyDownHandler, className: 'cio-result-card', role: 'button', tabIndex: 0 }, !customClickItemCallback ? resultCardContentWithLink() : resultCardContent()));
35
+ return (React.createElement("div", { onClick: () => clickHandler(), onKeyDown: (e) => keyDownHandler(e), className: 'cio-result-card', role: 'button', tabIndex: 0 }, !customClickItemCallback ? resultCardContentWithLink() : resultCardContent()));
55
36
  }
@@ -1,45 +1,22 @@
1
1
  import React, { useContext } from 'react';
2
2
  import RedoButton from '../RedoButton/RedoButton';
3
- import { QuestionTypes } from '../CioQuiz/actions';
4
3
  import QuizContext from '../CioQuiz/context';
5
4
  import ResultFilters from '../ResultFilters/ResultFilters';
6
5
  import ZeroResults from '../ZeroResults/ZeroResults';
7
6
  import Results from '../Results/Results';
8
7
  export default function ResultContainer(props) {
9
- const { options, resetQuizSessionId } = props;
10
- const { addToCartCallback, clickItemCallback, resultCardSalePriceKey, resultCardRegularPriceKey, } = options;
11
- const { resultsResponse, cioClient, dispatch } = useContext(QuizContext);
12
- const filterExpression = resultsResponse?.request?.collection_filter_expression;
13
- const zeroResults = !resultsResponse?.response?.results?.length;
8
+ const { options } = props;
9
+ const { resultCardSalePriceKey, resultCardRegularPriceKey } = options;
10
+ const { state, resetQuiz } = useContext(QuizContext);
11
+ const zeroResults = !state?.quiz.results?.response?.results?.length;
14
12
  const resultsTitle = zeroResults ? 'Oops, there are no results' : 'Here are your results';
15
- const onResetClick = () => {
16
- if (dispatch && resultsResponse) {
17
- resetQuizSessionId();
18
- dispatch({
19
- type: QuestionTypes.Reset,
20
- });
21
- }
22
- };
23
- if (resultsResponse && resultsResponse.request && resultsResponse.response) {
24
- /* eslint-disable @typescript-eslint/naming-convention */
25
- const { quiz_id, quiz_session_id, quiz_version_id, result_id, request: { section, page }, response: { total_num_results }, } = resultsResponse;
26
- /* eslint-enable @typescript-eslint/naming-convention */
27
- cioClient?.tracker.trackQuizResultsLoaded({
28
- quiz_id,
29
- quiz_version_id,
30
- quiz_session_id,
31
- url: window.location.href,
32
- section,
33
- result_count: total_num_results,
34
- result_page: page,
35
- result_id,
36
- });
13
+ if (state?.quiz.results) {
37
14
  return (React.createElement("div", { className: 'cio-results-container' },
38
15
  React.createElement("h1", { className: 'cio-results-title' }, resultsTitle),
39
16
  React.createElement("div", { className: 'cio-results-filter-and-redo-container' },
40
- React.createElement(ResultFilters, { filters: filterExpression }),
41
- React.createElement(RedoButton, { onClick: onResetClick })),
42
- !zeroResults && (React.createElement(Results, { addToCartCallback: addToCartCallback, clickItemCallback: clickItemCallback, resultCardSalePriceKey: resultCardSalePriceKey, resultCardRegularPriceKey: resultCardRegularPriceKey })),
17
+ React.createElement(ResultFilters, null),
18
+ React.createElement(RedoButton, { onClick: resetQuiz })),
19
+ !zeroResults && (React.createElement(Results, { resultCardSalePriceKey: resultCardSalePriceKey, resultCardRegularPriceKey: resultCardRegularPriceKey })),
43
20
  zeroResults && React.createElement(ZeroResults, null)));
44
21
  }
45
22
  return React.createElement("div", null, "Loading");
@@ -1,29 +1,11 @@
1
1
  import React, { useContext } from 'react';
2
2
  import QuizContext from '../CioQuiz/context';
3
3
  export default function ResultCtaButton(props) {
4
- const { item, callback, className, price } = props;
5
- const { resultsResponse, cioClient } = useContext(QuizContext);
6
- const clickHandler = (e) => {
7
- e.preventDefault();
8
- if (resultsResponse && resultsResponse.request && resultsResponse.response) {
9
- /* eslint-disable @typescript-eslint/naming-convention */
10
- const { quiz_id, quiz_session_id, quiz_version_id, request: { section }, } = resultsResponse;
11
- /* eslint-enable @typescript-eslint/naming-convention */
12
- cioClient?.tracker.trackQuizConversion({
13
- quiz_id,
14
- quiz_version_id,
15
- quiz_session_id,
16
- item_id: item.data?.id,
17
- item_name: item.value,
18
- section,
19
- variation_id: item.data?.variation_id,
20
- revenue: (price && String(price)) || undefined,
21
- });
22
- }
23
- if (callback && typeof callback === 'function') {
24
- e.stopPropagation();
25
- callback(item);
26
- }
27
- };
28
- return (React.createElement("button", { type: 'button', className: `cio-result-card-cta-button ${className || ''}`, onClick: clickHandler }, "Add to Cart"));
4
+ const { item, className, price } = props;
5
+ const { addToCart } = useContext(QuizContext);
6
+ return (React.createElement("button", { type: 'button', className: `cio-result-card-cta-button ${className || ''}`, onClick: (e) => {
7
+ if (addToCart) {
8
+ addToCart(e, item, price);
9
+ }
10
+ } }, "Add to Cart"));
29
11
  }
@@ -1,27 +1,9 @@
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))];
1
+ import React, { useContext } from 'react';
2
+ import QuizContext from '../CioQuiz/context';
3
+ function ResultFilters() {
4
+ const { state } = useContext(QuizContext);
23
5
  return (React.createElement("div", { className: 'cio-results-filter-container' },
24
6
  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))))));
7
+ React.createElement("div", { className: 'cio-results-filter-options' }, state?.quiz.resultsFilters?.map((filter) => (React.createElement("div", { className: 'cio-results-filter-option', key: filter }, filter))))));
26
8
  }
27
9
  export default ResultFilters;
@@ -2,8 +2,8 @@ import React, { useContext } from 'react';
2
2
  import QuizContext from '../CioQuiz/context';
3
3
  import ResultCard from '../ResultCard/ResultCard';
4
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, index) => (React.createElement(ResultCard, { result: result, key: result.data?.id, salePriceKey: resultCardSalePriceKey, regularPriceKey: resultCardRegularPriceKey, addToCartCallback: addToCartCallback, clickItemCallback: clickItemCallback, resultPosition: index + 1 })))));
5
+ const { resultCardSalePriceKey, resultCardRegularPriceKey } = props;
6
+ const { state } = useContext(QuizContext);
7
+ return (React.createElement("div", { className: 'cio-results' }, state?.quiz?.results?.response?.results?.map((result, index) => (React.createElement(ResultCard, { result: result, key: result.data?.id, salePriceKey: resultCardSalePriceKey, regularPriceKey: resultCardRegularPriceKey, resultPosition: index + 1 })))));
8
8
  }
9
9
  export default Results;
@@ -6,27 +6,29 @@ import { renderImages } from '../../utils';
6
6
  import { QuestionTypes } from '../CioQuiz/actions';
7
7
  import ControlBar from '../ControlBar/ControlBar';
8
8
  function SelectTypeQuestion() {
9
- const { questionResponse, state, quizNextHandler, quizBackHandler, isFirstQuestion } = useContext(QuizContext);
9
+ const { state, nextQuestion, previousQuestion } = useContext(QuizContext);
10
10
  let question;
11
11
  let type;
12
12
  let hasImages = false;
13
- if (questionResponse) {
14
- question = questionResponse.next_question;
13
+ if (state?.quiz.currentQuestion) {
14
+ question = state.quiz.currentQuestion.next_question;
15
15
  type = question.type;
16
- hasImages = questionResponse.next_question.options.some((option) => option.images);
16
+ hasImages = question.options.some((option) => option.images);
17
17
  }
18
18
  const [selected, setSelected] = useState({});
19
19
  const isDisabled = Object.keys(selected).length === 0;
20
20
  useEffect(() => {
21
- if (questionResponse?.next_question?.type) {
22
- const answers = state?.answerInputs?.[questionResponse.next_question.id] || [];
21
+ if (state?.quiz.currentQuestion?.next_question?.type) {
22
+ const nextQuestionId = state.quiz.currentQuestion.next_question.id;
23
+ const answers = state.answers?.inputs?.[nextQuestionId] || [];
23
24
  const prevSelected = {};
24
25
  answers?.forEach((answer) => {
25
26
  prevSelected[Number(answer)] = true;
26
27
  });
27
28
  setSelected(prevSelected);
28
29
  }
29
- }, [questionResponse, state?.answerInputs]);
30
+ // eslint-disable-next-line react-hooks/exhaustive-deps
31
+ }, [state?.quiz.currentQuestion?.next_question.id]);
30
32
  const toggleIdSelected = (id) => {
31
33
  if (type === QuestionTypes.SingleSelect) {
32
34
  setSelected({ [id]: true });
@@ -48,18 +50,9 @@ function SelectTypeQuestion() {
48
50
  }
49
51
  };
50
52
  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
- });
53
+ if (nextQuestion && !isDisabled && state?.quiz.currentQuestion) {
54
+ const selectedAnswers = Object.keys(selected).filter((key) => selected[Number(key)]);
55
+ nextQuestion(selectedAnswers);
63
56
  }
64
57
  };
65
58
  if (question) {
@@ -78,7 +71,7 @@ function SelectTypeQuestion() {
78
71
  }, role: 'button', tabIndex: 0, key: option.id },
79
72
  option.images ? renderImages(option.images, 'cio-question-option-image') : '',
80
73
  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 })));
74
+ React.createElement(ControlBar, { nextButtonHandler: onNextClick, isNextButtonDisabled: isDisabled, backButtonHandler: previousQuestion, showBackButton: !state?.quiz.isFirstQuestion, ctaButtonText: question?.cta_text })));
82
75
  }
83
76
  return null;
84
77
  }
@@ -1,10 +1,10 @@
1
1
  import React from 'react';
2
2
  import CTAButton from '../CTAButton/CTAButton';
3
3
  function ZeroResults(props) {
4
- const { onResetClick } = props;
4
+ const { resetQuizClickHandler } = props;
5
5
  return (React.createElement("div", { className: 'cio-zero-results' },
6
6
  React.createElement("h3", { className: 'cio-zero-results-subtitle' }, "Sorry, it seems like we couldn\u2019t find results based on your answers."),
7
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 })));
8
+ React.createElement(CTAButton, { ctaText: 'Try Again', onClick: resetQuizClickHandler })));
9
9
  }
10
10
  export default ZeroResults;
@@ -8,8 +8,9 @@ export const componentDescription = `- import \`CioQuiz\` to render in your JSX.
8
8
  - This component handles state management, data fetching, and rendering logic.
9
9
  - To use this component, \`quizId\`, \`resultsPageOptions\`, and one of \`apiKey\` or \`cioJsClient\` are required.
10
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
11
+ - \`onAddToCartClick\` is a callback function that will be called when the "Add to cart" button is clicked
12
+ - \`onQuizResultClick\` 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
+ - \`onQuizResultsLoaded\` is an optional callback function that will be called when the quiz results are loaded
13
14
  - \`resultCardRegularPriceKey\` is a parameter that specifies the metadata field name for the regular price
14
15
  - \`resultCardSalePriceKey\` is an optional parameter that specifies the metadata field name for the sale price
15
16
  - Use different props to configure the behavior of this component.
@@ -1,9 +1,10 @@
1
1
  import { useMemo } from 'react';
2
- import { getCioClient } from '../utils';
2
+ import { getCioClient } from '../services';
3
3
  const useCioClient = ({ apiKey, cioJsClient }) => {
4
4
  if (!apiKey && !cioJsClient) {
5
- console.error('Either apiKey or cioJsClient is required');
5
+ throw new Error('Either apiKey or cioJsClient is required');
6
6
  }
7
- return useMemo(() => cioJsClient || getCioClient(apiKey), [apiKey, cioJsClient]);
7
+ const memoizedCioClient = useMemo(() => cioJsClient || getCioClient(apiKey), [apiKey, cioJsClient]);
8
+ return memoizedCioClient;
8
9
  };
9
10
  export default useCioClient;