@constructor-io/constructorio-ui-quizzes 1.3.11 → 1.4.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 (124) hide show
  1. package/dist/constructorio-ui-quizzes-bundled.js +20 -20
  2. package/lib/cjs/components/BackButton/BackButton.js +12 -7
  3. package/lib/cjs/components/CTAButton/CTAButton.js +4 -3
  4. package/lib/cjs/components/CioQuiz/actions.js +1 -0
  5. package/lib/cjs/components/CioQuiz/index.js +16 -9
  6. package/lib/cjs/components/CioQuiz/quizApiReducer.js +15 -9
  7. package/lib/cjs/components/CioQuiz/quizLocalReducer.js +36 -8
  8. package/lib/cjs/components/ControlBar/ControlBar.js +6 -4
  9. package/lib/cjs/components/CoverTypeQuestion/CoverTypeQuestion.js +2 -7
  10. package/lib/cjs/components/OpenTextTypeQuestion/OpenTextTypeQuestion.js +4 -32
  11. package/lib/cjs/components/RedoButton/RedoButton.js +11 -4
  12. package/lib/cjs/components/ResultCard/ResultCard.js +3 -18
  13. package/lib/cjs/components/ResultContainer/ResultContainer.js +2 -2
  14. package/lib/cjs/components/ResultCtaButton/ResultCtaButton.js +8 -7
  15. package/lib/cjs/components/ResultHeroCard/ResultHeroCard.js +36 -0
  16. package/lib/cjs/components/SelectTypeQuestion/SelectTypeQuestion.js +6 -56
  17. package/lib/cjs/components/SessionPromptModal/SessionPromptModal.js +18 -6
  18. package/lib/cjs/components/ZeroResults/ZeroResults.js +5 -4
  19. package/lib/cjs/constants.js +40 -5
  20. package/lib/cjs/hooks/useConsoleErrors.js +2 -1
  21. package/lib/cjs/hooks/usePropsGetters/index.js +85 -0
  22. package/lib/cjs/hooks/usePropsGetters/useCoverQuestionProps.js +13 -0
  23. package/lib/cjs/hooks/usePropsGetters/useNextQuestionButtonProps.js +24 -0
  24. package/lib/cjs/hooks/usePropsGetters/useOpenTextInputProps.js +47 -0
  25. package/lib/cjs/hooks/usePropsGetters/usePreviousQuestionButtonProps.js +18 -0
  26. package/lib/cjs/hooks/usePropsGetters/useSelectInputProps.js +84 -0
  27. package/lib/cjs/hooks/useQuiz.js +14 -28
  28. package/lib/cjs/hooks/useQuizEvents/index.js +21 -16
  29. package/lib/cjs/hooks/useQuizEvents/useHydrateQuizLocalState.js +18 -0
  30. package/lib/cjs/hooks/useQuizEvents/useQuizAddToCart.js +2 -2
  31. package/lib/cjs/hooks/useQuizEvents/useQuizAnswerChangeHandler.js +48 -0
  32. package/lib/cjs/hooks/useQuizEvents/useQuizBackClick.js +5 -5
  33. package/lib/cjs/hooks/useQuizEvents/useQuizNextClick.js +13 -39
  34. package/lib/cjs/hooks/useQuizEvents/useQuizResetClick.js +20 -0
  35. package/lib/cjs/hooks/useQuizEvents/useQuizResultClick.js +2 -2
  36. package/lib/cjs/hooks/useQuizState/index.js +21 -0
  37. package/lib/cjs/hooks/{useQuizApiState.js → useQuizState/useQuizApiState.js} +15 -29
  38. package/lib/cjs/hooks/useQuizState/useQuizLocalState.js +30 -0
  39. package/lib/cjs/index.js +25 -0
  40. package/lib/cjs/services/index.js +3 -3
  41. package/lib/cjs/stories/Quiz/tests/mocks.js +69 -14
  42. package/lib/cjs/utils.js +23 -1
  43. package/lib/mjs/components/BackButton/BackButton.js +12 -7
  44. package/lib/mjs/components/CTAButton/CTAButton.js +4 -3
  45. package/lib/mjs/components/CioQuiz/actions.js +1 -0
  46. package/lib/mjs/components/CioQuiz/index.js +16 -9
  47. package/lib/mjs/components/CioQuiz/quizApiReducer.js +19 -7
  48. package/lib/mjs/components/CioQuiz/quizLocalReducer.js +36 -7
  49. package/lib/mjs/components/ControlBar/ControlBar.js +6 -4
  50. package/lib/mjs/components/CoverTypeQuestion/CoverTypeQuestion.js +2 -7
  51. package/lib/mjs/components/OpenTextTypeQuestion/OpenTextTypeQuestion.js +5 -32
  52. package/lib/mjs/components/RedoButton/RedoButton.js +11 -4
  53. package/lib/mjs/components/ResultCard/ResultCard.js +3 -15
  54. package/lib/mjs/components/ResultContainer/ResultContainer.js +2 -2
  55. package/lib/mjs/components/ResultCtaButton/ResultCtaButton.js +8 -7
  56. package/lib/mjs/components/ResultHeroCard/ResultHeroCard.js +31 -0
  57. package/lib/mjs/components/SelectTypeQuestion/SelectTypeQuestion.js +6 -55
  58. package/lib/mjs/components/SessionPromptModal/SessionPromptModal.js +18 -6
  59. package/lib/mjs/components/ZeroResults/ZeroResults.js +5 -4
  60. package/lib/mjs/constants.js +39 -4
  61. package/lib/mjs/hooks/useConsoleErrors.js +2 -1
  62. package/lib/mjs/hooks/usePropsGetters/index.js +72 -0
  63. package/lib/mjs/hooks/usePropsGetters/useCoverQuestionProps.js +10 -0
  64. package/lib/mjs/hooks/usePropsGetters/useNextQuestionButtonProps.js +20 -0
  65. package/lib/mjs/hooks/usePropsGetters/useOpenTextInputProps.js +43 -0
  66. package/lib/mjs/hooks/usePropsGetters/usePreviousQuestionButtonProps.js +14 -0
  67. package/lib/mjs/hooks/usePropsGetters/useSelectInputProps.js +79 -0
  68. package/lib/mjs/hooks/useQuiz.js +13 -21
  69. package/lib/mjs/hooks/useQuizEvents/index.js +21 -16
  70. package/lib/mjs/hooks/useQuizEvents/useHydrateQuizLocalState.js +16 -0
  71. package/lib/mjs/hooks/useQuizEvents/useQuizAddToCart.js +2 -2
  72. package/lib/mjs/hooks/useQuizEvents/useQuizAnswerChangeHandler.js +45 -0
  73. package/lib/mjs/hooks/useQuizEvents/useQuizBackClick.js +5 -5
  74. package/lib/mjs/hooks/useQuizEvents/useQuizNextClick.js +12 -38
  75. package/lib/mjs/hooks/useQuizEvents/useQuizResetClick.js +18 -0
  76. package/lib/mjs/hooks/useQuizEvents/useQuizResultClick.js +2 -2
  77. package/lib/mjs/hooks/useQuizState/index.js +18 -0
  78. package/lib/mjs/hooks/{useQuizApiState.js → useQuizState/useQuizApiState.js} +15 -28
  79. package/lib/mjs/hooks/useQuizState/useQuizLocalState.js +26 -0
  80. package/lib/mjs/index.js +14 -0
  81. package/lib/mjs/services/index.js +1 -1
  82. package/lib/mjs/stories/Quiz/tests/mocks.js +75 -12
  83. package/lib/mjs/utils.js +17 -0
  84. package/lib/styles.css +13 -2
  85. package/lib/types/components/BackButton/BackButton.d.ts +2 -2
  86. package/lib/types/components/CTAButton/CTAButton.d.ts +1 -0
  87. package/lib/types/components/CioQuiz/actions.d.ts +5 -6
  88. package/lib/types/components/CioQuiz/context.d.ts +13 -6
  89. package/lib/types/components/CioQuiz/quizApiReducer.d.ts +2 -2
  90. package/lib/types/components/CioQuiz/quizLocalReducer.d.ts +3 -5
  91. package/lib/types/components/ControlBar/ControlBar.d.ts +0 -4
  92. package/lib/types/components/OpenTextTypeQuestion/OpenTextTypeQuestion.d.ts +2 -6
  93. package/lib/types/components/RedoButton/RedoButton.d.ts +1 -1
  94. package/lib/types/components/ResultCtaButton/ResultCtaButton.d.ts +1 -2
  95. package/lib/types/components/ResultHeroCard/ResultHeroCard.d.ts +7 -0
  96. package/lib/types/components/SelectTypeQuestion/SelectTypeQuestion.d.ts +3 -0
  97. package/lib/types/components/ZeroResults/ZeroResults.d.ts +2 -5
  98. package/lib/types/constants.d.ts +5 -4
  99. package/lib/types/hooks/useConsoleErrors.d.ts +2 -2
  100. package/lib/types/hooks/usePropsGetters/index.d.ts +18 -0
  101. package/lib/types/hooks/usePropsGetters/useCoverQuestionProps.d.ts +2 -0
  102. package/lib/types/hooks/usePropsGetters/useNextQuestionButtonProps.d.ts +4 -0
  103. package/lib/types/hooks/usePropsGetters/useOpenTextInputProps.d.ts +2 -0
  104. package/lib/types/hooks/usePropsGetters/usePreviousQuestionButtonProps.d.ts +3 -0
  105. package/lib/types/hooks/usePropsGetters/useSelectInputProps.d.ts +2 -0
  106. package/lib/types/hooks/useQuizEvents/index.d.ts +4 -16
  107. package/lib/types/hooks/useQuizEvents/useHydrateQuizLocalState.d.ts +4 -0
  108. package/lib/types/hooks/useQuizEvents/useQuizAnswerChangeHandler.d.ts +5 -0
  109. package/lib/types/hooks/useQuizEvents/useQuizBackClick.d.ts +2 -1
  110. package/lib/types/hooks/useQuizEvents/useQuizNextClick.d.ts +2 -1
  111. package/lib/types/hooks/useQuizEvents/useQuizResetClick.d.ts +4 -0
  112. package/lib/types/hooks/useQuizState/index.d.ts +16 -0
  113. package/lib/types/hooks/useQuizState/useQuizApiState.d.ts +12 -0
  114. package/lib/types/hooks/useQuizState/useQuizLocalState.d.ts +8 -0
  115. package/lib/types/index.d.ts +11 -0
  116. package/lib/types/services/index.d.ts +1 -1
  117. package/lib/types/stories/Quiz/tests/mocks.d.ts +5 -2
  118. package/lib/types/types.d.ts +121 -11
  119. package/lib/types/utils.d.ts +4 -0
  120. package/package.json +1 -1
  121. package/lib/cjs/hooks/useQuizLocalState.js +0 -54
  122. package/lib/mjs/hooks/useQuizLocalState.js +0 -48
  123. package/lib/types/hooks/useQuizApiState.d.ts +0 -11
  124. package/lib/types/hooks/useQuizLocalState.d.ts +0 -10
@@ -0,0 +1,79 @@
1
+ import { useState, useCallback, useEffect, useRef } from 'react';
2
+ import { QuestionTypes } from '../../components/CioQuiz/actions';
3
+ // eslint-disable-next-line max-params
4
+ export default function useSelectInputProps(quizAnswerChanged, nextQuestion, currentQuestionData, answerInputs) {
5
+ const type = currentQuestionData?.type;
6
+ const hasImages = currentQuestionData?.options?.some((option) => option.images);
7
+ const [selected, setSelected] = useState({});
8
+ const singleSelectClicked = useRef({});
9
+ const toggleIdSelected = useCallback((id) => {
10
+ if (type === QuestionTypes.SingleSelect) {
11
+ singleSelectClicked.current = true;
12
+ setSelected({ [id]: true });
13
+ }
14
+ else if (type === QuestionTypes.MultipleSelect) {
15
+ if (selected[id]) {
16
+ const newState = { ...selected };
17
+ delete newState[id];
18
+ setSelected(newState);
19
+ }
20
+ else {
21
+ setSelected({ ...selected, [id]: true });
22
+ }
23
+ }
24
+ }, [selected, type]);
25
+ const onOptionKeyDown = (event, id) => {
26
+ if (event?.key === ' ' || event?.key === 'Enter') {
27
+ toggleIdSelected(id);
28
+ }
29
+ };
30
+ // Update component local state
31
+ useEffect(() => {
32
+ if (currentQuestionData?.type) {
33
+ const nextQuestionId = currentQuestionData.id;
34
+ const currentAnswer = answerInputs?.[nextQuestionId];
35
+ const prevSelected = {};
36
+ if (Array.isArray(currentAnswer?.value)) {
37
+ currentAnswer?.value?.forEach((answer) => {
38
+ prevSelected[Number(answer)] = true;
39
+ setSelected(prevSelected);
40
+ });
41
+ }
42
+ }
43
+ singleSelectClicked.current = false;
44
+ return function clearState() {
45
+ setSelected({});
46
+ };
47
+ // eslint-disable-next-line react-hooks/exhaustive-deps
48
+ }, [currentQuestionData?.id]);
49
+ // Update global state
50
+ useEffect(() => {
51
+ if (currentQuestionData?.type === 'multiple' || currentQuestionData?.type === 'single') {
52
+ const selectedAnswers = Object.keys(selected).filter((key) => selected[Number(key)]);
53
+ quizAnswerChanged(selectedAnswers);
54
+ }
55
+ }, [selected, currentQuestionData?.id, currentQuestionData?.type, quizAnswerChanged]);
56
+ // Go to next question only every time answerInputs (answers input state) changes...
57
+ // and it's a singleSelectQuestion and user has just clicked on an option
58
+ useEffect(() => {
59
+ if (currentQuestionData?.type === 'single' && singleSelectClicked.current) {
60
+ nextQuestion();
61
+ }
62
+ // eslint-disable-next-line react-hooks/exhaustive-deps
63
+ }, [answerInputs]);
64
+ const getSelectInputProps = useCallback((option) => ({
65
+ className: `${!hasImages ? 'cio-question-option-container-text-only' : 'cio-question-option-container'} ${selected[option.id] ? 'selected' : ''}`,
66
+ onClick: () => {
67
+ toggleIdSelected(option.id);
68
+ },
69
+ onKeyDown: (event) => {
70
+ onOptionKeyDown(event, option.id);
71
+ },
72
+ role: 'button',
73
+ tabIndex: 0,
74
+ key: option.id,
75
+ }),
76
+ // eslint-disable-next-line react-hooks/exhaustive-deps
77
+ [currentQuestionData?.id, selected]);
78
+ return getSelectInputProps;
79
+ }
@@ -1,31 +1,24 @@
1
1
  import useCioClient from './useCioClient';
2
2
  import useConsoleErrors from './useConsoleErrors';
3
+ import usePropsGetters from './usePropsGetters';
3
4
  import usePrimaryColorStyles from './usePrimaryColorStyles';
4
- import useQuizApiState from './useQuizApiState';
5
5
  import useQuizEvents from './useQuizEvents';
6
- import useQuizLocalState from './useQuizLocalState';
7
- const useQuiz = ({ quizId, apiKey, cioJsClient, quizVersionId, resultsPageOptions, sessionStateOptions, primaryColor, }) => {
6
+ import useQuizState from './useQuizState';
7
+ const useQuiz = (quizOptions) => {
8
+ const { apiKey, cioJsClient, primaryColor } = quizOptions;
8
9
  // Log console errors for required parameters quizId and resultsPageOptions
9
- useConsoleErrors(quizId, resultsPageOptions);
10
- // Quiz Local state
11
- const { quizLocalState, resetQuizLocalState, dispatchLocalState, hydrateQuizLocalState, hasQuizStoredState, resetQuizStoredState, } = useQuizLocalState(sessionStateOptions?.sessionStateKey);
10
+ useConsoleErrors(quizOptions);
12
11
  // Quiz Cio Client
13
12
  const cioClient = useCioClient({ apiKey, cioJsClient });
14
- // Quiz API state
15
- const { isFirstQuestion, quizApiState, resetQuizApiState } = useQuizApiState(quizId, quizLocalState, dispatchLocalState, resultsPageOptions, quizVersionId, cioClient);
13
+ // Quiz state (Local and API)
14
+ const quizState = useQuizState(quizOptions, cioClient);
16
15
  // Quiz callback events
17
- const quizEvents = useQuizEvents({
18
- cioClient,
19
- quizApiState,
20
- resultsPageOptions,
21
- dispatchLocalState,
22
- resetQuizApiState,
23
- resetQuizLocalState,
24
- hydrateQuizLocalState,
25
- resetQuizStoredState,
26
- hasQuizStoredState,
27
- });
16
+ const quizEvents = useQuizEvents(quizOptions, cioClient, quizState);
17
+ // Props getters
18
+ const { quizApiState, quizLocalState } = quizState;
19
+ const propGetters = usePropsGetters(quizEvents, quizApiState, quizLocalState);
28
20
  const primaryColorStyles = usePrimaryColorStyles(primaryColor);
21
+ console.log('quizLocalState.answerInputs', quizLocalState.answerInputs);
29
22
  return {
30
23
  cioClient,
31
24
  state: {
@@ -37,16 +30,15 @@ const useQuiz = ({ quizId, apiKey, cioJsClient, quizVersionId, resultsPageOption
37
30
  requestState: quizApiState.quizRequestState,
38
31
  versionId: quizLocalState.quizVersionId,
39
32
  sessionId: quizLocalState.quizSessionId,
40
- firstQuestion: quizApiState.quizFirstQuestion,
41
33
  currentQuestion: quizApiState.quizCurrentQuestion,
42
34
  results: quizApiState.quizResults,
43
35
  resultsFilters: quizApiState.quizResultsFilters,
44
- isFirstQuestion,
45
36
  },
46
37
  },
47
38
  events: {
48
39
  ...quizEvents,
49
40
  },
41
+ ...propGetters,
50
42
  primaryColorStyles,
51
43
  };
52
44
  };
@@ -1,37 +1,42 @@
1
1
  import useQuizResultsLoaded from './useQuizResultsLoaded';
2
2
  import useQuizResultClick from './useQuizResultClick';
3
3
  import useQuizAddToCart from './useQuizAddToCart';
4
- import useQuizNextClick from './useQuizNextClick';
5
4
  import useQuizBackClick from './useQuizBackClick';
6
- const useQuizEvents = (options) => {
7
- const { cioClient, quizApiState, resultsPageOptions, dispatchLocalState, resetQuizApiState, resetQuizLocalState, hydrateQuizLocalState, resetQuizStoredState, hasQuizStoredState, } = options;
5
+ import useQuizAnswerChangeHandler from './useQuizAnswerChangeHandler';
6
+ import useQuizNextClick from './useQuizNextClick';
7
+ import useQuizResetClick from './useQuizResetClick';
8
+ import useHydrateQuizLocalState from './useHydrateQuizLocalState';
9
+ import { resetQuizSessionStorageState } from '../../utils';
10
+ const useQuizEvents = (quizOptions, cioClient, quizState) => {
11
+ const { quizApiState, dispatchLocalState, dispatchApiState, hasQuizStoredState, quizStateKey, quizLocalState, } = quizState;
12
+ const { resultsPageOptions } = quizOptions;
8
13
  const { onAddToCartClick, onQuizResultClick, onQuizResultsLoaded } = resultsPageOptions;
9
- // Quiz Next button click
10
- const nextQuestion = useQuizNextClick(quizApiState, dispatchLocalState);
14
+ // Quiz answer change
15
+ const quizAnswerChanged = useQuizAnswerChangeHandler(quizApiState, dispatchLocalState);
16
+ // Quiz Next button click callback
17
+ const nextQuestion = useQuizNextClick(quizApiState, quizLocalState, dispatchLocalState);
11
18
  // Quiz Back button click callback
12
- const previousQuestion = useQuizBackClick(dispatchLocalState);
19
+ const previousQuestion = useQuizBackClick(quizApiState, dispatchLocalState);
13
20
  // Quiz result add to cart callback
14
21
  const addToCart = useQuizAddToCart(cioClient, quizApiState, onAddToCartClick);
15
22
  // Quiz result click callback
16
23
  const resultClick = useQuizResultClick(cioClient, quizApiState, onQuizResultClick);
17
24
  // Quiz results loaded event
18
25
  useQuizResultsLoaded(cioClient, quizApiState, onQuizResultsLoaded);
19
- const resetQuiz = () => {
20
- if (quizApiState.quizResults) {
21
- resetQuizApiState();
22
- resetQuizLocalState();
23
- resetQuizStoredState();
24
- }
25
- };
26
+ // Quiz reset
27
+ const resetQuiz = useQuizResetClick(quizStateKey, dispatchLocalState, dispatchApiState, quizApiState.quizResults);
28
+ // Quiz rehydrate
29
+ const hydrateQuizLocalState = useHydrateQuizLocalState(quizStateKey, dispatchLocalState);
26
30
  return {
27
31
  addToCart,
28
32
  resultClick,
29
- nextQuestion,
33
+ quizAnswerChanged,
30
34
  previousQuestion,
35
+ nextQuestion,
31
36
  resetQuiz,
32
37
  hydrateQuiz: hydrateQuizLocalState,
33
- hasStoredState: hasQuizStoredState,
34
- resetStoredState: resetQuizStoredState,
38
+ hasSessionStorageState: hasQuizStoredState,
39
+ resetSessionStorageState: resetQuizSessionStorageState(quizStateKey),
35
40
  };
36
41
  };
37
42
  export default useQuizEvents;
@@ -0,0 +1,16 @@
1
+ import { useCallback } from 'react';
2
+ import { QuestionTypes } from '../../components/CioQuiz/actions';
3
+ import { getStateFromSessionStorage } from '../../utils';
4
+ const useHydrateQuizLocalState = (quizStateKey, dispatchLocalState) => {
5
+ const quizState = getStateFromSessionStorage(quizStateKey);
6
+ const hydrateQuizLocalStateHandler = useCallback(() => {
7
+ if (quizState) {
8
+ dispatchLocalState({
9
+ type: QuestionTypes.Hydrate,
10
+ payload: quizState,
11
+ });
12
+ }
13
+ }, [dispatchLocalState, quizState]);
14
+ return hydrateQuizLocalStateHandler;
15
+ };
16
+ export default useHydrateQuizLocalState;
@@ -2,7 +2,7 @@ import { useCallback } from 'react';
2
2
  import { trackQuizConversion } from '../../services';
3
3
  import { isFunction } from '../../utils';
4
4
  const useQuizAddToCart = (cioClient, quizApiState, onAddToCartClick) => {
5
- const addToCartClickHandler = useCallback((e, result, price) => {
5
+ const quizAddToCartClickHandler = useCallback((e, result, price) => {
6
6
  e.preventDefault();
7
7
  if (quizApiState.quizResults) {
8
8
  // Tracking call
@@ -13,6 +13,6 @@ const useQuizAddToCart = (cioClient, quizApiState, onAddToCartClick) => {
13
13
  }
14
14
  }
15
15
  }, [quizApiState, cioClient, onAddToCartClick]);
16
- return addToCartClickHandler;
16
+ return quizAddToCartClickHandler;
17
17
  };
18
18
  export default useQuizAddToCart;
@@ -0,0 +1,45 @@
1
+ import { useCallback } from 'react';
2
+ import { QuestionTypes } from '../../components/CioQuiz/actions';
3
+ const useQuizAnswerChangeHandler = (quizApiState, dispatchLocalState) => {
4
+ const quizAnswerChangedHandler = useCallback((payload) => {
5
+ const questionType = quizApiState.quizCurrentQuestion?.next_question.type;
6
+ const currentQuestion = quizApiState.quizCurrentQuestion;
7
+ switch (questionType) {
8
+ case QuestionTypes.Cover:
9
+ dispatchLocalState({
10
+ type: QuestionTypes.Cover,
11
+ payload: {
12
+ questionId: currentQuestion.next_question.id,
13
+ input: '',
14
+ isLastQuestion: currentQuestion.is_last_question,
15
+ },
16
+ });
17
+ break;
18
+ case QuestionTypes.OpenText:
19
+ dispatchLocalState({
20
+ type: QuestionTypes.OpenText,
21
+ payload: {
22
+ questionId: currentQuestion.next_question.id,
23
+ input: payload,
24
+ isLastQuestion: currentQuestion.is_last_question,
25
+ },
26
+ });
27
+ break;
28
+ case QuestionTypes.SingleSelect:
29
+ case QuestionTypes.MultipleSelect:
30
+ dispatchLocalState({
31
+ type: currentQuestion.next_question.type,
32
+ payload: {
33
+ questionId: currentQuestion.next_question.id,
34
+ input: payload,
35
+ isLastQuestion: currentQuestion.is_last_question,
36
+ },
37
+ });
38
+ break;
39
+ default:
40
+ break;
41
+ }
42
+ }, [quizApiState, dispatchLocalState]);
43
+ return quizAnswerChangedHandler;
44
+ };
45
+ export default useQuizAnswerChangeHandler;
@@ -1,11 +1,11 @@
1
1
  import { useCallback } from 'react';
2
2
  import { QuestionTypes } from '../../components/CioQuiz/actions';
3
- const useQuizBackClick = (dispatchLocalState) => {
4
- const quizBackHandler = useCallback(() => {
3
+ const useQuizBackClick = (quizApiState, dispatchLocalState) => {
4
+ const quizBackClickHandler = useCallback(() => {
5
5
  if (dispatchLocalState) {
6
- dispatchLocalState({ type: QuestionTypes.Back });
6
+ dispatchLocalState({ type: QuestionTypes.Back, payload: quizApiState.quizCurrentQuestion });
7
7
  }
8
- }, [dispatchLocalState]);
9
- return quizBackHandler;
8
+ }, [dispatchLocalState, quizApiState.quizCurrentQuestion]);
9
+ return quizBackClickHandler;
10
10
  };
11
11
  export default useQuizBackClick;
@@ -1,45 +1,19 @@
1
1
  import { useCallback } from 'react';
2
2
  import { QuestionTypes } from '../../components/CioQuiz/actions';
3
- const useQuizNextClick = (quizApiState, dispatchLocalState) => {
4
- const quizNextHandler = useCallback((payload) => {
5
- const questionType = quizApiState.quizCurrentQuestion?.next_question.type;
6
- const currentQuestion = quizApiState.quizCurrentQuestion;
7
- switch (questionType) {
8
- case QuestionTypes.Cover:
3
+ const useQuizNextClick = (quizApiState, quizLocalState, dispatchLocalState) => {
4
+ const quizNexClickHandler = useCallback(() => {
5
+ const currentQuestion = quizApiState.quizCurrentQuestion?.next_question;
6
+ const currentQuestionId = currentQuestion?.id;
7
+ if (dispatchLocalState && currentQuestionId) {
8
+ const currentAnswerInput = quizLocalState.answerInputs[currentQuestionId];
9
+ if (currentAnswerInput?.value?.length || currentQuestion?.type === 'cover') {
9
10
  dispatchLocalState({
10
- type: QuestionTypes.Cover,
11
- payload: {
12
- isLastQuestion: currentQuestion.is_last_question,
13
- },
11
+ type: QuestionTypes.Next,
12
+ payload: quizApiState.quizCurrentQuestion,
14
13
  });
15
- break;
16
- case QuestionTypes.OpenText:
17
- dispatchLocalState({
18
- type: QuestionTypes.OpenText,
19
- payload: {
20
- questionId: currentQuestion.next_question.id,
21
- input: payload,
22
- isLastQuestion: currentQuestion.is_last_question,
23
- },
24
- });
25
- break;
26
- case QuestionTypes.SingleSelect:
27
- case QuestionTypes.MultipleSelect:
28
- dispatchLocalState({
29
- type: currentQuestion.next_question.type === QuestionTypes.SingleSelect
30
- ? QuestionTypes.SingleSelect
31
- : QuestionTypes.MultipleSelect,
32
- payload: {
33
- questionId: currentQuestion.next_question.id,
34
- input: payload,
35
- isLastQuestion: currentQuestion.is_last_question,
36
- },
37
- });
38
- break;
39
- default:
40
- break;
14
+ }
41
15
  }
42
- }, [quizApiState, dispatchLocalState]);
43
- return quizNextHandler;
16
+ }, [dispatchLocalState, quizApiState.quizCurrentQuestion, quizLocalState.answerInputs]);
17
+ return quizNexClickHandler;
44
18
  };
45
19
  export default useQuizNextClick;
@@ -0,0 +1,18 @@
1
+ import { useCallback } from 'react';
2
+ import { QuestionTypes, QuizAPIActionTypes, } from '../../components/CioQuiz/actions';
3
+ import { resetQuizSessionStorageState } from '../../utils';
4
+ const useQuizResetClick = (quizStateKey, dispatchLocalState, dispatchApiState, quizResults) => {
5
+ const quizResetClickHandler = useCallback(() => {
6
+ if (quizResults) {
7
+ dispatchLocalState({
8
+ type: QuestionTypes.Reset,
9
+ });
10
+ dispatchApiState({
11
+ type: QuizAPIActionTypes.RESET_QUIZ,
12
+ });
13
+ resetQuizSessionStorageState(quizStateKey);
14
+ }
15
+ }, [dispatchLocalState, dispatchApiState, quizStateKey, quizResults]);
16
+ return quizResetClickHandler;
17
+ };
18
+ export default useQuizResetClick;
@@ -2,7 +2,7 @@ import { useCallback } from 'react';
2
2
  import { trackQuizResultClick } from '../../services';
3
3
  import { isFunction } from '../../utils';
4
4
  const useQuizResultClick = (cioClient, quizApiState, onQuizResultClick) => {
5
- const resultClickHandler = useCallback((result, position) => {
5
+ const quizResultClickHandler = useCallback((result, position) => {
6
6
  if (quizApiState.quizResults) {
7
7
  // Tracking call
8
8
  trackQuizResultClick(cioClient, quizApiState.quizResults, result, position);
@@ -12,6 +12,6 @@ const useQuizResultClick = (cioClient, quizApiState, onQuizResultClick) => {
12
12
  }
13
13
  }
14
14
  }, [quizApiState, cioClient, onQuizResultClick]);
15
- return resultClickHandler;
15
+ return quizResultClickHandler;
16
16
  };
17
17
  export default useQuizResultClick;
@@ -0,0 +1,18 @@
1
+ import useQuizApiState from './useQuizApiState';
2
+ import useQuizLocalState from './useQuizLocalState';
3
+ const useQuizState = (quizOptions, cioClient) => {
4
+ const { sessionStateOptions } = quizOptions;
5
+ // Quiz Local state
6
+ const { quizLocalState, dispatchLocalState, hasQuizStoredState, quizStateKey } = useQuizLocalState(sessionStateOptions?.sessionStateKey);
7
+ // Quiz API state
8
+ const { quizApiState, dispatchApiState } = useQuizApiState(quizOptions, cioClient, quizLocalState, dispatchLocalState);
9
+ return {
10
+ quizApiState,
11
+ quizLocalState,
12
+ dispatchApiState,
13
+ dispatchLocalState,
14
+ hasQuizStoredState,
15
+ quizStateKey,
16
+ };
17
+ };
18
+ export default useQuizState;
@@ -1,15 +1,13 @@
1
1
  import { useEffect, useReducer } from 'react';
2
- import { QuestionTypes, QuizAPIActionTypes, } from '../components/CioQuiz/actions';
3
- import apiReducer, { initialState } from '../components/CioQuiz/quizApiReducer';
4
- import { nextQuestion, getQuizResults } from '../services';
5
- const useFetchQuiz = (quizId, quizLocalState, dispatchLocalState, resultsPageOptions, quizVersionIdProp, cioClient) => {
6
- const [quizApiState, dispatch] = useReducer(apiReducer, initialState);
7
- const firstQuestionId = quizApiState.quizFirstQuestion?.next_question.id;
8
- const currentQuestionId = quizApiState.quizCurrentQuestion?.next_question.id;
9
- const isFirstQuestion = firstQuestionId === currentQuestionId;
2
+ import { QuestionTypes, QuizAPIActionTypes, } from '../../components/CioQuiz/actions';
3
+ import apiReducer, { initialState, } from '../../components/CioQuiz/quizApiReducer';
4
+ import { getNextQuestion, getQuizResults } from '../../services';
5
+ const useQuizApiState = (quizOptions, cioClient, quizLocalState, dispatchLocalState) => {
6
+ const [quizApiState, dispatchApiState] = useReducer(apiReducer, initialState);
7
+ const { quizId, quizVersionId: quizVersionIdProp, resultsPageOptions } = quizOptions;
10
8
  useEffect(() => {
11
9
  (async () => {
12
- dispatch({
10
+ dispatchApiState({
13
11
  type: QuizAPIActionTypes.SET_IS_LOADING,
14
12
  });
15
13
  if (quizLocalState.isLastAnswer) {
@@ -21,7 +19,7 @@ const useFetchQuiz = (quizId, quizLocalState, dispatchLocalState, resultsPageOpt
21
19
  quizSessionId: quizLocalState.quizSessionId,
22
20
  });
23
21
  // Set quiz results state
24
- dispatch({
22
+ dispatchApiState({
25
23
  type: QuizAPIActionTypes.SET_QUIZ_RESULTS,
26
24
  payload: {
27
25
  quizResults,
@@ -29,7 +27,7 @@ const useFetchQuiz = (quizId, quizLocalState, dispatchLocalState, resultsPageOpt
29
27
  });
30
28
  }
31
29
  catch (error) {
32
- dispatch({
30
+ dispatchApiState({
33
31
  type: QuizAPIActionTypes.SET_IS_ERROR,
34
32
  });
35
33
  }
@@ -38,7 +36,7 @@ const useFetchQuiz = (quizId, quizLocalState, dispatchLocalState, resultsPageOpt
38
36
  try {
39
37
  const quizVersionId = quizLocalState.quizVersionId || quizVersionIdProp;
40
38
  const { quizSessionId } = quizLocalState;
41
- const questionResult = await nextQuestion(cioClient, quizId, {
39
+ const questionResult = await getNextQuestion(cioClient, quizId, {
42
40
  answers: quizLocalState.answers,
43
41
  quizVersionId,
44
42
  quizSessionId,
@@ -55,7 +53,7 @@ const useFetchQuiz = (quizId, quizLocalState, dispatchLocalState, resultsPageOpt
55
53
  });
56
54
  }
57
55
  // Set current question state
58
- dispatch({
56
+ dispatchApiState({
59
57
  type: QuizAPIActionTypes.SET_CURRENT_QUESTION,
60
58
  payload: {
61
59
  quizCurrentQuestion: questionResult,
@@ -63,28 +61,17 @@ const useFetchQuiz = (quizId, quizLocalState, dispatchLocalState, resultsPageOpt
63
61
  });
64
62
  }
65
63
  catch (error) {
66
- dispatch({
64
+ dispatchApiState({
67
65
  type: QuizAPIActionTypes.SET_IS_ERROR,
68
66
  });
69
67
  }
70
68
  }
71
69
  })();
72
70
  // eslint-disable-next-line react-hooks/exhaustive-deps
73
- }, [
74
- cioClient,
75
- quizId,
76
- quizLocalState.answers,
77
- quizLocalState.isLastAnswer,
78
- resultsPageOptions?.numResultsToDisplay,
79
- ]);
80
- const resetQuizApiState = () => {
81
- dispatch({ type: QuizAPIActionTypes.RESET_QUIZ });
82
- };
71
+ }, [cioClient, quizId, quizLocalState.answers, resultsPageOptions?.numResultsToDisplay]);
83
72
  return {
84
- cioClient,
85
73
  quizApiState,
86
- isFirstQuestion,
87
- resetQuizApiState,
74
+ dispatchApiState,
88
75
  };
89
76
  };
90
- export default useFetchQuiz;
77
+ export default useQuizApiState;
@@ -0,0 +1,26 @@
1
+ import { useCallback, useEffect, useReducer } from 'react';
2
+ import quizLocalReducer, { initialState } from '../../components/CioQuiz/quizLocalReducer';
3
+ import { quizSessionStateKey } from '../../constants';
4
+ import { getStateFromSessionStorage, logger } from '../../utils';
5
+ const useQuizLocalState = (sessionStateKey) => {
6
+ const [quizLocalState, dispatch] = useReducer(quizLocalReducer, initialState);
7
+ const quizStateKey = sessionStateKey || quizSessionStateKey;
8
+ useEffect(() => {
9
+ // don't save state if initial state
10
+ if (quizLocalState?.answers?.length) {
11
+ window?.sessionStorage?.setItem(quizStateKey, JSON.stringify(quizLocalState));
12
+ }
13
+ }, [quizLocalState, quizStateKey]);
14
+ const hasQuizStoredState = () => getStateFromSessionStorage(quizStateKey) !== null;
15
+ const dispatchLocalState = useCallback((action) => {
16
+ logger(action);
17
+ dispatch(action);
18
+ }, []);
19
+ return {
20
+ quizLocalState,
21
+ hasQuizStoredState,
22
+ dispatchLocalState,
23
+ quizStateKey,
24
+ };
25
+ };
26
+ export default useQuizLocalState;
package/lib/mjs/index.js CHANGED
@@ -1,2 +1,16 @@
1
1
  import CioQuiz from './components/CioQuiz';
2
+ // Hook
3
+ export { default as useCioQuiz } from './hooks/useQuiz';
4
+ // Questions Components
5
+ export { default as QuizQuestions } from './components/QuizQuestions/index';
6
+ export { default as OpenTextQuestion } from './components/OpenTextTypeQuestion/OpenTextTypeQuestion';
7
+ export { default as CoverQuestion } from './components/CoverTypeQuestion/CoverTypeQuestion';
8
+ export { default as SelectQuestion } from './components/SelectTypeQuestion/SelectTypeQuestion';
9
+ // Results Components
10
+ export { default as Results } from './components/Results/Results';
11
+ export { default as ResultCard } from './components/ResultCard/ResultCard';
12
+ export { default as ResultContainer } from './components/ResultContainer/ResultContainer';
13
+ export { default as ResultFilters } from './components/ResultFilters/ResultFilters';
14
+ export { default as ResultHeroCard } from './components/ResultHeroCard/ResultHeroCard';
15
+ export * from './types';
2
16
  export default CioQuiz;
@@ -9,7 +9,7 @@ export const getCioClient = (apiKey) => {
9
9
  }
10
10
  return undefined;
11
11
  };
12
- export const nextQuestion = (cioClient, quizId, parameters) => cioClient?.quizzes.getQuizNextQuestion(quizId, parameters);
12
+ export const getNextQuestion = (cioClient, quizId, parameters) => cioClient?.quizzes.getQuizNextQuestion(quizId, parameters);
13
13
  export const getQuizResults = async (cioClient, quizId, parameters) => cioClient?.quizzes.getQuizResults(quizId, parameters);
14
14
  // Tracking requests
15
15
  export const trackQuizResultsLoaded = (cioClient, quizResults) => {