@constructor-io/constructorio-ui-quizzes 1.4.4 → 1.4.6

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 (41) hide show
  1. package/README.md +14 -0
  2. package/dist/constructorio-ui-quizzes-bundled.js +13 -13
  3. package/lib/cjs/components/CioQuiz/actions.js +1 -0
  4. package/lib/cjs/components/CioQuiz/index.js +7 -5
  5. package/lib/cjs/components/CioQuiz/quizLocalReducer.js +9 -6
  6. package/lib/cjs/components/RedoButton/RedoButton.js +1 -1
  7. package/lib/cjs/components/ResultCard/ResultCard.js +1 -1
  8. package/lib/cjs/components/ResultContainer/ResultContainer.js +3 -1
  9. package/lib/cjs/components/ZeroResults/ZeroResults.js +1 -1
  10. package/lib/cjs/constants.js +4 -0
  11. package/lib/cjs/hooks/useQuiz.js +2 -1
  12. package/lib/cjs/hooks/useQuizEvents/index.js +1 -1
  13. package/lib/cjs/hooks/useQuizEvents/useQuizResetClick.js +3 -4
  14. package/lib/cjs/hooks/useQuizState/index.js +3 -2
  15. package/lib/cjs/hooks/useQuizState/useQuizApiState.js +8 -2
  16. package/lib/cjs/hooks/useQuizState/useQuizLocalState.js +6 -2
  17. package/lib/cjs/stories/Quiz/tests/mocks.js +1 -0
  18. package/lib/mjs/components/CioQuiz/actions.js +1 -0
  19. package/lib/mjs/components/CioQuiz/index.js +7 -5
  20. package/lib/mjs/components/CioQuiz/quizLocalReducer.js +12 -0
  21. package/lib/mjs/components/RedoButton/RedoButton.js +1 -1
  22. package/lib/mjs/components/ResultCard/ResultCard.js +1 -1
  23. package/lib/mjs/components/ResultContainer/ResultContainer.js +3 -1
  24. package/lib/mjs/components/ZeroResults/ZeroResults.js +1 -1
  25. package/lib/mjs/constants.js +4 -0
  26. package/lib/mjs/hooks/useQuiz.js +2 -1
  27. package/lib/mjs/hooks/useQuizEvents/index.js +1 -1
  28. package/lib/mjs/hooks/useQuizEvents/useQuizResetClick.js +3 -4
  29. package/lib/mjs/hooks/useQuizState/index.js +3 -2
  30. package/lib/mjs/hooks/useQuizState/useQuizApiState.js +8 -2
  31. package/lib/mjs/hooks/useQuizState/useQuizLocalState.js +5 -2
  32. package/lib/mjs/stories/Quiz/tests/mocks.js +1 -0
  33. package/lib/types/components/CioQuiz/actions.d.ts +3 -2
  34. package/lib/types/components/CioQuiz/quizLocalReducer.d.ts +1 -0
  35. package/lib/types/constants.d.ts +1 -1
  36. package/lib/types/hooks/useQuizEvents/useQuizResetClick.d.ts +1 -1
  37. package/lib/types/hooks/useQuizState/index.d.ts +1 -0
  38. package/lib/types/hooks/useQuizState/useQuizApiState.d.ts +1 -1
  39. package/lib/types/hooks/useQuizState/useQuizLocalState.d.ts +3 -1
  40. package/lib/types/types.d.ts +2 -0
  41. package/package.json +5 -1
@@ -12,6 +12,7 @@ var QuestionTypes;
12
12
  QuestionTypes["Back"] = "back";
13
13
  QuestionTypes["Reset"] = "reset";
14
14
  QuestionTypes["Hydrate"] = "hydrate";
15
+ QuestionTypes["Complete"] = "complete";
15
16
  })(QuestionTypes = exports.QuestionTypes || (exports.QuestionTypes = {}));
16
17
  // API actions
17
18
  var QuizAPIActionTypes;
@@ -17,11 +17,15 @@ function CioQuiz(props) {
17
17
  (0, react_1.useEffect)(() => {
18
18
  // Respect showSessionModal if defined, else default to true.
19
19
  if ((sessionStateOptions === null || sessionStateOptions === void 0 ? void 0 : sessionStateOptions.showSessionModal) !== undefined) {
20
- setShowSessionPrompt((sessionStateOptions === null || sessionStateOptions === void 0 ? void 0 : sessionStateOptions.showSessionModal) && hasSessionStorageState());
20
+ setShowSessionPrompt((sessionStateOptions === null || sessionStateOptions === void 0 ? void 0 : sessionStateOptions.showSessionModal) &&
21
+ hasSessionStorageState() &&
22
+ !state.quiz.skipToResults);
21
23
  }
22
24
  else {
23
- setShowSessionPrompt(hasSessionStorageState());
25
+ setShowSessionPrompt(hasSessionStorageState() && !state.quiz.skipToResults);
24
26
  }
27
+ if (state.quiz.skipToResults)
28
+ hydrateQuiz();
25
29
  // eslint-disable-next-line react-hooks/exhaustive-deps
26
30
  }, []);
27
31
  const contextValue = {
@@ -51,9 +55,7 @@ function CioQuiz(props) {
51
55
  ".cio-quiz ",
52
56
  (0, utils_1.convertPrimaryColorsToString)(primaryColorStyles)),
53
57
  react_1.default.createElement(SessionPromptModal_1.default, { resetStoredState: resetSessionStorageState, continueSession: hydrateQuiz, showSessionPrompt: showSessionPrompt, setShowSessionPrompt: setShowSessionPrompt }),
54
- react_1.default.createElement(context_1.default.Provider, { value: contextValue },
55
- state.quiz.results && react_1.default.createElement(ResultContainer_1.default, { options: resultsPageOptions }),
56
- state.quiz.currentQuestion && react_1.default.createElement(QuizQuestions_1.default, null))));
58
+ react_1.default.createElement(context_1.default.Provider, { value: contextValue }, state.quiz.results || state.quiz.skipToResults ? (react_1.default.createElement(ResultContainer_1.default, { options: resultsPageOptions })) : (state.quiz.currentQuestion && react_1.default.createElement(QuizQuestions_1.default, null)))));
57
59
  }
58
60
  return null;
59
61
  }
@@ -6,6 +6,7 @@ exports.initialState = {
6
6
  answers: [],
7
7
  answerInputs: {},
8
8
  isLastAnswer: false,
9
+ isQuizCompleted: false,
9
10
  };
10
11
  function answerInputReducer(state, action) {
11
12
  return Object.assign(Object.assign({}, state), { [String(action.payload.questionId)]: {
@@ -17,13 +18,13 @@ function quizLocalReducer(state, action) {
17
18
  var _a, _b, _c, _d, _e;
18
19
  switch (action.type) {
19
20
  case actions_1.QuestionTypes.OpenText:
20
- return Object.assign(Object.assign({}, state), { answerInputs: answerInputReducer(state.answerInputs, action), isLastAnswer: !!((_a = action.payload) === null || _a === void 0 ? void 0 : _a.isLastQuestion) });
21
+ return Object.assign(Object.assign({}, state), { answerInputs: answerInputReducer(state.answerInputs, action), isLastAnswer: !!((_a = action.payload) === null || _a === void 0 ? void 0 : _a.isLastQuestion), isQuizCompleted: false });
21
22
  case actions_1.QuestionTypes.Cover:
22
- return Object.assign(Object.assign({}, state), { answerInputs: answerInputReducer(state.answerInputs, action), isLastAnswer: !!((_b = action.payload) === null || _b === void 0 ? void 0 : _b.isLastQuestion) });
23
+ return Object.assign(Object.assign({}, state), { answerInputs: answerInputReducer(state.answerInputs, action), isLastAnswer: !!((_b = action.payload) === null || _b === void 0 ? void 0 : _b.isLastQuestion), isQuizCompleted: false });
23
24
  case actions_1.QuestionTypes.SingleSelect:
24
- return Object.assign(Object.assign({}, state), { answerInputs: answerInputReducer(state.answerInputs, action), isLastAnswer: !!((_c = action.payload) === null || _c === void 0 ? void 0 : _c.isLastQuestion) });
25
+ return Object.assign(Object.assign({}, state), { answerInputs: answerInputReducer(state.answerInputs, action), isLastAnswer: !!((_c = action.payload) === null || _c === void 0 ? void 0 : _c.isLastQuestion), isQuizCompleted: false });
25
26
  case actions_1.QuestionTypes.MultipleSelect:
26
- return Object.assign(Object.assign({}, state), { answerInputs: answerInputReducer(state.answerInputs, action), isLastAnswer: !!((_d = action.payload) === null || _d === void 0 ? void 0 : _d.isLastQuestion) });
27
+ return Object.assign(Object.assign({}, state), { answerInputs: answerInputReducer(state.answerInputs, action), isLastAnswer: !!((_d = action.payload) === null || _d === void 0 ? void 0 : _d.isLastQuestion), isQuizCompleted: false });
27
28
  case actions_1.QuestionTypes.Next: {
28
29
  const { answers } = state;
29
30
  const newAnswers = [...answers];
@@ -45,16 +46,18 @@ function quizLocalReducer(state, action) {
45
46
  default:
46
47
  newAnswers.push([]);
47
48
  }
48
- return Object.assign(Object.assign({}, state), { answers: newAnswers });
49
+ return Object.assign(Object.assign({}, state), { answers: newAnswers, isQuizCompleted: false });
49
50
  }
50
51
  case actions_1.QuestionTypes.Back: {
51
52
  const newAnswerInputs = Object.assign({}, state.answerInputs);
52
- return Object.assign(Object.assign({}, state), { answerInputs: newAnswerInputs, answers: [...state.answers.slice(0, -1)], isLastAnswer: false });
53
+ return Object.assign(Object.assign({}, state), { answerInputs: newAnswerInputs, answers: [...state.answers.slice(0, -1)], isLastAnswer: false, isQuizCompleted: false });
53
54
  }
54
55
  case actions_1.QuestionTypes.Reset:
55
56
  return Object.assign({}, exports.initialState);
56
57
  case actions_1.QuestionTypes.Hydrate:
57
58
  return Object.assign(Object.assign({}, state), action.payload);
59
+ case actions_1.QuestionTypes.Complete:
60
+ return Object.assign(Object.assign({}, state), { isQuizCompleted: true });
58
61
  default:
59
62
  return state;
60
63
  }
@@ -5,7 +5,7 @@ const react_1 = tslib_1.__importStar(require("react"));
5
5
  const RedoSVG_1 = tslib_1.__importDefault(require("./RedoSVG"));
6
6
  const context_1 = tslib_1.__importDefault(require("../CioQuiz/context"));
7
7
  function RedoButton(props) {
8
- const { redoText = 'Redo Quiz', disabled } = props, rest = tslib_1.__rest(props, ["redoText", "disabled"]);
8
+ const { redoText = 'Retake Quiz', disabled } = props, rest = tslib_1.__rest(props, ["redoText", "disabled"]);
9
9
  const { getResetQuizButtonProps } = (0, react_1.useContext)(context_1.default);
10
10
  if (getResetQuizButtonProps) {
11
11
  return (
@@ -37,7 +37,7 @@ function ResultCard(props) {
37
37
  regularPrice)))))));
38
38
  };
39
39
  const resultCardContentWithoutLink = () => getQuizResultButtonProps && (react_1.default.createElement("div", Object.assign({}, getQuizResultButtonProps({ result, position: resultPosition, type: 'button' })), resultCardContent()));
40
- const resultCardContentWithLink = () => getQuizResultLinkProps && (react_1.default.createElement("a", Object.assign({ className: 'cio-result-card-anchor' }, getQuizResultLinkProps({ result, position: resultPosition, type: 'link' })), resultCardContent()));
40
+ const resultCardContentWithLink = () => getQuizResultLinkProps && (react_1.default.createElement("a", Object.assign({ className: 'cio-result-card-anchor', rel: 'noreferrer', target: '_blank' }, getQuizResultLinkProps({ result, position: resultPosition, type: 'link' })), resultCardContent()));
41
41
  return (react_1.default.createElement("div", { className: 'cio-result-card' },
42
42
  !customClickItemCallback ? resultCardContentWithLink() : resultCardContentWithoutLink(),
43
43
  react_1.default.createElement(ResultCtaButton_1.default, { item: result, price: salePrice || regularPrice })));
@@ -7,6 +7,7 @@ const context_1 = tslib_1.__importDefault(require("../CioQuiz/context"));
7
7
  const ResultFilters_1 = tslib_1.__importDefault(require("../ResultFilters/ResultFilters"));
8
8
  const ZeroResults_1 = tslib_1.__importDefault(require("../ZeroResults/ZeroResults"));
9
9
  const Results_1 = tslib_1.__importDefault(require("../Results/Results"));
10
+ const Spinner_1 = tslib_1.__importDefault(require("../Spinner/Spinner"));
10
11
  function ResultContainer(props) {
11
12
  var _a, _b, _c;
12
13
  const { options } = props;
@@ -23,6 +24,7 @@ function ResultContainer(props) {
23
24
  !zeroResults && (react_1.default.createElement(Results_1.default, { resultCardSalePriceKey: resultCardSalePriceKey, resultCardRegularPriceKey: resultCardRegularPriceKey, resultCardRatingCountKey: resultCardRatingCountKey, resultCardRatingScoreKey: resultCardRatingScoreKey })),
24
25
  zeroResults && react_1.default.createElement(ZeroResults_1.default, null)));
25
26
  }
26
- return react_1.default.createElement("div", null, "Loading");
27
+ return (react_1.default.createElement("div", { className: 'cio-results-container' },
28
+ react_1.default.createElement(Spinner_1.default, null)));
27
29
  }
28
30
  exports.default = ResultContainer;
@@ -8,6 +8,6 @@ function ZeroResults() {
8
8
  const { getResetQuizButtonProps } = (0, react_1.useContext)(context_1.default);
9
9
  return (react_1.default.createElement("div", { className: 'cio-zero-results' },
10
10
  react_1.default.createElement("h3", { className: 'cio-zero-results-subtitle' }, "Sorry, we couldn\u2019t find products that perfectly match your preferences."),
11
- react_1.default.createElement(CTAButton_1.default, { ctaText: 'Redo Quiz', propsGetters: getResetQuizButtonProps })));
11
+ react_1.default.createElement(CTAButton_1.default, { ctaText: 'Retake Quiz', propsGetters: getResetQuizButtonProps })));
12
12
  }
13
13
  exports.default = ZeroResults;
@@ -20,6 +20,10 @@ exports.componentDescription = `- import \`CioQuiz\` to render in your JSX.
20
20
  - \`resultCardSalePriceKey\` is an optional parameter that specifies the metadata field name for the sale price
21
21
  - \`resultCardRatingCountKey\` is an optional parameter that specifies the metadata field name for the ratings count
22
22
  - \`resultCardRatingScoreKey\` is an optional parameter that specifies the metadata field name for the ratings score
23
+ - \`sessionStateOptions\` lets you configure the session modal behavior
24
+ - \`showSessionModal\` is a boolean used to decide whether to show the session modal. The default behavior is to show the session modal
25
+ - \`showSessionModalOnResults\` is a boolean to decide whether to show the session modal after reaching the results page. The default behavior is to not show the session modal
26
+ - \`sessionStateKey\` is a custom string that will be used as a session storage key
23
27
  - Use different props to configure the behavior of this component.
24
28
  - The following stories show how different props affect the component's behavior
25
29
 
@@ -18,7 +18,7 @@ const useQuiz = (quizOptions) => {
18
18
  // Quiz callback events
19
19
  const quizEvents = (0, useQuizEvents_1.default)(quizOptions, cioClient, quizState);
20
20
  // Props getters
21
- const { quizApiState, quizLocalState } = quizState;
21
+ const { quizApiState, quizLocalState, skipToResults } = quizState;
22
22
  const propGetters = (0, usePropsGetters_1.default)(quizEvents, quizApiState, quizLocalState);
23
23
  const primaryColorStyles = (0, usePrimaryColorStyles_1.default)(primaryColor);
24
24
  return Object.assign(Object.assign({ cioClient, state: {
@@ -30,6 +30,7 @@ const useQuiz = (quizOptions) => {
30
30
  requestState: quizApiState.quizRequestState,
31
31
  versionId: quizLocalState.quizVersionId,
32
32
  sessionId: quizLocalState.quizSessionId,
33
+ skipToResults,
33
34
  currentQuestion: quizApiState.quizCurrentQuestion,
34
35
  results: quizApiState.quizResults,
35
36
  selectedOptionsWithAttributes: quizApiState.selectedOptionsWithAttributes,
@@ -27,7 +27,7 @@ const useQuizEvents = (quizOptions, cioClient, quizState) => {
27
27
  // Quiz results loaded event
28
28
  (0, useQuizResultsLoaded_1.default)(cioClient, quizApiState, onQuizResultsLoaded);
29
29
  // Quiz reset
30
- const resetQuiz = (0, useQuizResetClick_1.default)(quizStateKey, dispatchLocalState, dispatchApiState, quizApiState.quizResults);
30
+ const resetQuiz = (0, useQuizResetClick_1.default)((0, utils_1.resetQuizSessionStorageState)(quizStateKey), dispatchLocalState, dispatchApiState, quizApiState.quizResults);
31
31
  // Quiz rehydrate
32
32
  const hydrateQuizLocalState = (0, useHydrateQuizLocalState_1.default)(quizStateKey, dispatchLocalState);
33
33
  return {
@@ -2,8 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const react_1 = require("react");
4
4
  const actions_1 = require("../../components/CioQuiz/actions");
5
- const utils_1 = require("../../utils");
6
- const useQuizResetClick = (quizStateKey, dispatchLocalState, dispatchApiState, quizResults) => {
5
+ const useQuizResetClick = (resetQuizSessionStorageState, dispatchLocalState, dispatchApiState, quizResults) => {
7
6
  const quizResetClickHandler = (0, react_1.useCallback)(() => {
8
7
  if (quizResults) {
9
8
  dispatchLocalState({
@@ -12,9 +11,9 @@ const useQuizResetClick = (quizStateKey, dispatchLocalState, dispatchApiState, q
12
11
  dispatchApiState({
13
12
  type: actions_1.QuizAPIActionTypes.RESET_QUIZ,
14
13
  });
15
- (0, utils_1.resetQuizSessionStorageState)(quizStateKey);
14
+ resetQuizSessionStorageState();
16
15
  }
17
- }, [dispatchLocalState, dispatchApiState, quizStateKey, quizResults]);
16
+ }, [dispatchLocalState, dispatchApiState, resetQuizSessionStorageState, quizResults]);
18
17
  return quizResetClickHandler;
19
18
  };
20
19
  exports.default = useQuizResetClick;
@@ -6,15 +6,16 @@ const useQuizLocalState_1 = tslib_1.__importDefault(require("./useQuizLocalState
6
6
  const useQuizState = (quizOptions, cioClient) => {
7
7
  const { sessionStateOptions } = quizOptions;
8
8
  // Quiz Local state
9
- const { quizLocalState, dispatchLocalState, hasQuizStoredState, quizStateKey } = (0, useQuizLocalState_1.default)(sessionStateOptions === null || sessionStateOptions === void 0 ? void 0 : sessionStateOptions.sessionStateKey);
9
+ const { quizLocalState, dispatchLocalState, hasQuizStoredState, skipToResults, quizStateKey } = (0, useQuizLocalState_1.default)(sessionStateOptions);
10
10
  // Quiz API state
11
- const { quizApiState, dispatchApiState } = (0, useQuizApiState_1.default)(quizOptions, cioClient, quizLocalState, dispatchLocalState);
11
+ const { quizApiState, dispatchApiState } = (0, useQuizApiState_1.default)(quizOptions, cioClient, quizLocalState, skipToResults, dispatchLocalState);
12
12
  return {
13
13
  quizApiState,
14
14
  quizLocalState,
15
15
  dispatchApiState,
16
16
  dispatchLocalState,
17
17
  hasQuizStoredState,
18
+ skipToResults,
18
19
  quizStateKey,
19
20
  };
20
21
  };
@@ -5,7 +5,9 @@ const react_1 = require("react");
5
5
  const actions_1 = require("../../components/CioQuiz/actions");
6
6
  const quizApiReducer_1 = tslib_1.__importStar(require("../../components/CioQuiz/quizApiReducer"));
7
7
  const services_1 = require("../../services");
8
- const useQuizApiState = (quizOptions, cioClient, quizLocalState, dispatchLocalState) => {
8
+ const useQuizApiState = (quizOptions, cioClient, quizLocalState, skipToResults, dispatchLocalState
9
+ // eslint-disable-next-line max-params
10
+ ) => {
9
11
  const [quizApiState, dispatchApiState] = (0, react_1.useReducer)(quizApiReducer_1.default, quizApiReducer_1.initialState);
10
12
  const { quizId, quizVersionId: quizVersionIdProp, resultsPageOptions } = quizOptions;
11
13
  (0, react_1.useEffect)(() => {
@@ -13,7 +15,7 @@ const useQuizApiState = (quizOptions, cioClient, quizLocalState, dispatchLocalSt
13
15
  dispatchApiState({
14
16
  type: actions_1.QuizAPIActionTypes.SET_IS_LOADING,
15
17
  });
16
- if (quizLocalState.isLastAnswer) {
18
+ if (quizLocalState.isLastAnswer || skipToResults) {
17
19
  try {
18
20
  const quizResults = yield (0, services_1.getQuizResults)(cioClient, quizId, {
19
21
  answers: quizLocalState.answers,
@@ -28,6 +30,10 @@ const useQuizApiState = (quizOptions, cioClient, quizLocalState, dispatchLocalSt
28
30
  quizResults,
29
31
  },
30
32
  });
33
+ if (!quizLocalState.isQuizCompleted)
34
+ dispatchLocalState({
35
+ type: actions_1.QuestionTypes.Complete,
36
+ });
31
37
  }
32
38
  catch (error) {
33
39
  dispatchApiState({
@@ -5,9 +5,10 @@ const react_1 = require("react");
5
5
  const quizLocalReducer_1 = tslib_1.__importStar(require("../../components/CioQuiz/quizLocalReducer"));
6
6
  const constants_1 = require("../../constants");
7
7
  const utils_1 = require("../../utils");
8
- const useQuizLocalState = (sessionStateKey) => {
8
+ const useQuizLocalState = (sessionStateOptions) => {
9
+ var _a;
9
10
  const [quizLocalState, dispatch] = (0, react_1.useReducer)(quizLocalReducer_1.default, quizLocalReducer_1.initialState);
10
- const quizStateKey = sessionStateKey || constants_1.quizSessionStateKey;
11
+ const quizStateKey = (sessionStateOptions === null || sessionStateOptions === void 0 ? void 0 : sessionStateOptions.sessionStateKey) || constants_1.quizSessionStateKey;
11
12
  (0, react_1.useEffect)(() => {
12
13
  var _a, _b;
13
14
  // don't save state if initial state
@@ -16,6 +17,8 @@ const useQuizLocalState = (sessionStateKey) => {
16
17
  }
17
18
  }, [quizLocalState, quizStateKey]);
18
19
  const hasQuizStoredState = () => (0, utils_1.getStateFromSessionStorage)(quizStateKey) !== null;
20
+ const skipToResults = !!((_a = (0, utils_1.getStateFromSessionStorage)(quizStateKey)) === null || _a === void 0 ? void 0 : _a.isQuizCompleted) &&
21
+ !(sessionStateOptions === null || sessionStateOptions === void 0 ? void 0 : sessionStateOptions.showSessionModalOnResults);
19
22
  const dispatchLocalState = (0, react_1.useCallback)((action) => {
20
23
  (0, utils_1.logger)(action);
21
24
  dispatch(action);
@@ -24,6 +27,7 @@ const useQuizLocalState = (sessionStateKey) => {
24
27
  quizLocalState,
25
28
  hasQuizStoredState,
26
29
  dispatchLocalState,
30
+ skipToResults,
27
31
  quizStateKey,
28
32
  };
29
33
  };
@@ -55,6 +55,7 @@ const getMockState = (question) => ({
55
55
  requestState: constants_1.RequestStates.Success,
56
56
  versionId: '',
57
57
  sessionId: '',
58
+ skipToResults: false,
58
59
  currentQuestion: {
59
60
  next_question: question,
60
61
  isFirstQuestion: false,
@@ -9,6 +9,7 @@ export var QuestionTypes;
9
9
  QuestionTypes["Back"] = "back";
10
10
  QuestionTypes["Reset"] = "reset";
11
11
  QuestionTypes["Hydrate"] = "hydrate";
12
+ QuestionTypes["Complete"] = "complete";
12
13
  })(QuestionTypes || (QuestionTypes = {}));
13
14
  // API actions
14
15
  export var QuizAPIActionTypes;
@@ -14,11 +14,15 @@ export default function CioQuiz(props) {
14
14
  useEffect(() => {
15
15
  // Respect showSessionModal if defined, else default to true.
16
16
  if (sessionStateOptions?.showSessionModal !== undefined) {
17
- setShowSessionPrompt(sessionStateOptions?.showSessionModal && hasSessionStorageState());
17
+ setShowSessionPrompt(sessionStateOptions?.showSessionModal &&
18
+ hasSessionStorageState() &&
19
+ !state.quiz.skipToResults);
18
20
  }
19
21
  else {
20
- setShowSessionPrompt(hasSessionStorageState());
22
+ setShowSessionPrompt(hasSessionStorageState() && !state.quiz.skipToResults);
21
23
  }
24
+ if (state.quiz.skipToResults)
25
+ hydrateQuiz();
22
26
  // eslint-disable-next-line react-hooks/exhaustive-deps
23
27
  }, []);
24
28
  const contextValue = {
@@ -48,9 +52,7 @@ export default function CioQuiz(props) {
48
52
  ".cio-quiz ",
49
53
  convertPrimaryColorsToString(primaryColorStyles)),
50
54
  React.createElement(SessionPromptModal, { resetStoredState: resetSessionStorageState, continueSession: hydrateQuiz, showSessionPrompt: showSessionPrompt, setShowSessionPrompt: setShowSessionPrompt }),
51
- React.createElement(QuizContext.Provider, { value: contextValue },
52
- state.quiz.results && React.createElement(ResultContainer, { options: resultsPageOptions }),
53
- state.quiz.currentQuestion && React.createElement(QuizQuestions, null))));
55
+ React.createElement(QuizContext.Provider, { value: contextValue }, state.quiz.results || state.quiz.skipToResults ? (React.createElement(ResultContainer, { options: resultsPageOptions })) : (state.quiz.currentQuestion && React.createElement(QuizQuestions, null)))));
54
56
  }
55
57
  return null;
56
58
  }
@@ -3,6 +3,7 @@ export const initialState = {
3
3
  answers: [],
4
4
  answerInputs: {},
5
5
  isLastAnswer: false,
6
+ isQuizCompleted: false,
6
7
  };
7
8
  function answerInputReducer(state, action) {
8
9
  return {
@@ -20,24 +21,28 @@ export default function quizLocalReducer(state, action) {
20
21
  ...state,
21
22
  answerInputs: answerInputReducer(state.answerInputs, action),
22
23
  isLastAnswer: !!action.payload?.isLastQuestion,
24
+ isQuizCompleted: false,
23
25
  };
24
26
  case QuestionTypes.Cover:
25
27
  return {
26
28
  ...state,
27
29
  answerInputs: answerInputReducer(state.answerInputs, action),
28
30
  isLastAnswer: !!action.payload?.isLastQuestion,
31
+ isQuizCompleted: false,
29
32
  };
30
33
  case QuestionTypes.SingleSelect:
31
34
  return {
32
35
  ...state,
33
36
  answerInputs: answerInputReducer(state.answerInputs, action),
34
37
  isLastAnswer: !!action.payload?.isLastQuestion,
38
+ isQuizCompleted: false,
35
39
  };
36
40
  case QuestionTypes.MultipleSelect:
37
41
  return {
38
42
  ...state,
39
43
  answerInputs: answerInputReducer(state.answerInputs, action),
40
44
  isLastAnswer: !!action.payload?.isLastQuestion,
45
+ isQuizCompleted: false,
41
46
  };
42
47
  case QuestionTypes.Next: {
43
48
  const { answers } = state;
@@ -63,6 +68,7 @@ export default function quizLocalReducer(state, action) {
63
68
  return {
64
69
  ...state,
65
70
  answers: newAnswers,
71
+ isQuizCompleted: false,
66
72
  };
67
73
  }
68
74
  case QuestionTypes.Back: {
@@ -72,6 +78,7 @@ export default function quizLocalReducer(state, action) {
72
78
  answerInputs: newAnswerInputs,
73
79
  answers: [...state.answers.slice(0, -1)],
74
80
  isLastAnswer: false,
81
+ isQuizCompleted: false,
75
82
  };
76
83
  }
77
84
  case QuestionTypes.Reset:
@@ -83,6 +90,11 @@ export default function quizLocalReducer(state, action) {
83
90
  ...state,
84
91
  ...action.payload,
85
92
  };
93
+ case QuestionTypes.Complete:
94
+ return {
95
+ ...state,
96
+ isQuizCompleted: true,
97
+ };
86
98
  default:
87
99
  return state;
88
100
  }
@@ -2,7 +2,7 @@ import React, { useContext } from 'react';
2
2
  import RedoSVG from './RedoSVG';
3
3
  import QuizContext from '../CioQuiz/context';
4
4
  function RedoButton(props) {
5
- const { redoText = 'Redo Quiz', disabled, ...rest } = props;
5
+ const { redoText = 'Retake Quiz', disabled, ...rest } = props;
6
6
  const { getResetQuizButtonProps } = useContext(QuizContext);
7
7
  if (getResetQuizButtonProps) {
8
8
  return (
@@ -30,7 +30,7 @@ export default function ResultCard(props) {
30
30
  "$",
31
31
  regularPrice)))))));
32
32
  const resultCardContentWithoutLink = () => getQuizResultButtonProps && (React.createElement("div", { ...getQuizResultButtonProps({ result, position: resultPosition, type: 'button' }) }, resultCardContent()));
33
- const resultCardContentWithLink = () => getQuizResultLinkProps && (React.createElement("a", { className: 'cio-result-card-anchor', ...getQuizResultLinkProps({ result, position: resultPosition, type: 'link' }) }, resultCardContent()));
33
+ const resultCardContentWithLink = () => getQuizResultLinkProps && (React.createElement("a", { className: 'cio-result-card-anchor', rel: 'noreferrer', target: '_blank', ...getQuizResultLinkProps({ result, position: resultPosition, type: 'link' }) }, resultCardContent()));
34
34
  return (React.createElement("div", { className: 'cio-result-card' },
35
35
  !customClickItemCallback ? resultCardContentWithLink() : resultCardContentWithoutLink(),
36
36
  React.createElement(ResultCtaButton, { item: result, price: salePrice || regularPrice })));
@@ -4,6 +4,7 @@ import QuizContext from '../CioQuiz/context';
4
4
  import ResultFilters from '../ResultFilters/ResultFilters';
5
5
  import ZeroResults from '../ZeroResults/ZeroResults';
6
6
  import Results from '../Results/Results';
7
+ import Spinner from '../Spinner/Spinner';
7
8
  export default function ResultContainer(props) {
8
9
  const { options } = props;
9
10
  const { resultCardSalePriceKey, resultCardRegularPriceKey, resultCardRatingCountKey, resultCardRatingScoreKey, } = options;
@@ -19,5 +20,6 @@ export default function ResultContainer(props) {
19
20
  !zeroResults && (React.createElement(Results, { resultCardSalePriceKey: resultCardSalePriceKey, resultCardRegularPriceKey: resultCardRegularPriceKey, resultCardRatingCountKey: resultCardRatingCountKey, resultCardRatingScoreKey: resultCardRatingScoreKey })),
20
21
  zeroResults && React.createElement(ZeroResults, null)));
21
22
  }
22
- return React.createElement("div", null, "Loading");
23
+ return (React.createElement("div", { className: 'cio-results-container' },
24
+ React.createElement(Spinner, null)));
23
25
  }
@@ -5,6 +5,6 @@ function ZeroResults() {
5
5
  const { getResetQuizButtonProps } = useContext(QuizContext);
6
6
  return (React.createElement("div", { className: 'cio-zero-results' },
7
7
  React.createElement("h3", { className: 'cio-zero-results-subtitle' }, "Sorry, we couldn\u2019t find products that perfectly match your preferences."),
8
- React.createElement(CTAButton, { ctaText: 'Redo Quiz', propsGetters: getResetQuizButtonProps })));
8
+ React.createElement(CTAButton, { ctaText: 'Retake Quiz', propsGetters: getResetQuizButtonProps })));
9
9
  }
10
10
  export default ZeroResults;
@@ -17,6 +17,10 @@ export const componentDescription = `- import \`CioQuiz\` to render in your JSX.
17
17
  - \`resultCardSalePriceKey\` is an optional parameter that specifies the metadata field name for the sale price
18
18
  - \`resultCardRatingCountKey\` is an optional parameter that specifies the metadata field name for the ratings count
19
19
  - \`resultCardRatingScoreKey\` is an optional parameter that specifies the metadata field name for the ratings score
20
+ - \`sessionStateOptions\` lets you configure the session modal behavior
21
+ - \`showSessionModal\` is a boolean used to decide whether to show the session modal. The default behavior is to show the session modal
22
+ - \`showSessionModalOnResults\` is a boolean to decide whether to show the session modal after reaching the results page. The default behavior is to not show the session modal
23
+ - \`sessionStateKey\` is a custom string that will be used as a session storage key
20
24
  - Use different props to configure the behavior of this component.
21
25
  - The following stories show how different props affect the component's behavior
22
26
 
@@ -15,7 +15,7 @@ const useQuiz = (quizOptions) => {
15
15
  // Quiz callback events
16
16
  const quizEvents = useQuizEvents(quizOptions, cioClient, quizState);
17
17
  // Props getters
18
- const { quizApiState, quizLocalState } = quizState;
18
+ const { quizApiState, quizLocalState, skipToResults } = quizState;
19
19
  const propGetters = usePropsGetters(quizEvents, quizApiState, quizLocalState);
20
20
  const primaryColorStyles = usePrimaryColorStyles(primaryColor);
21
21
  return {
@@ -29,6 +29,7 @@ const useQuiz = (quizOptions) => {
29
29
  requestState: quizApiState.quizRequestState,
30
30
  versionId: quizLocalState.quizVersionId,
31
31
  sessionId: quizLocalState.quizSessionId,
32
+ skipToResults,
32
33
  currentQuestion: quizApiState.quizCurrentQuestion,
33
34
  results: quizApiState.quizResults,
34
35
  selectedOptionsWithAttributes: quizApiState.selectedOptionsWithAttributes,
@@ -24,7 +24,7 @@ const useQuizEvents = (quizOptions, cioClient, quizState) => {
24
24
  // Quiz results loaded event
25
25
  useQuizResultsLoaded(cioClient, quizApiState, onQuizResultsLoaded);
26
26
  // Quiz reset
27
- const resetQuiz = useQuizResetClick(quizStateKey, dispatchLocalState, dispatchApiState, quizApiState.quizResults);
27
+ const resetQuiz = useQuizResetClick(resetQuizSessionStorageState(quizStateKey), dispatchLocalState, dispatchApiState, quizApiState.quizResults);
28
28
  // Quiz rehydrate
29
29
  const hydrateQuizLocalState = useHydrateQuizLocalState(quizStateKey, dispatchLocalState);
30
30
  return {
@@ -1,7 +1,6 @@
1
1
  import { useCallback } from 'react';
2
2
  import { QuestionTypes, QuizAPIActionTypes, } from '../../components/CioQuiz/actions';
3
- import { resetQuizSessionStorageState } from '../../utils';
4
- const useQuizResetClick = (quizStateKey, dispatchLocalState, dispatchApiState, quizResults) => {
3
+ const useQuizResetClick = (resetQuizSessionStorageState, dispatchLocalState, dispatchApiState, quizResults) => {
5
4
  const quizResetClickHandler = useCallback(() => {
6
5
  if (quizResults) {
7
6
  dispatchLocalState({
@@ -10,9 +9,9 @@ const useQuizResetClick = (quizStateKey, dispatchLocalState, dispatchApiState, q
10
9
  dispatchApiState({
11
10
  type: QuizAPIActionTypes.RESET_QUIZ,
12
11
  });
13
- resetQuizSessionStorageState(quizStateKey);
12
+ resetQuizSessionStorageState();
14
13
  }
15
- }, [dispatchLocalState, dispatchApiState, quizStateKey, quizResults]);
14
+ }, [dispatchLocalState, dispatchApiState, resetQuizSessionStorageState, quizResults]);
16
15
  return quizResetClickHandler;
17
16
  };
18
17
  export default useQuizResetClick;
@@ -3,15 +3,16 @@ import useQuizLocalState from './useQuizLocalState';
3
3
  const useQuizState = (quizOptions, cioClient) => {
4
4
  const { sessionStateOptions } = quizOptions;
5
5
  // Quiz Local state
6
- const { quizLocalState, dispatchLocalState, hasQuizStoredState, quizStateKey } = useQuizLocalState(sessionStateOptions?.sessionStateKey);
6
+ const { quizLocalState, dispatchLocalState, hasQuizStoredState, skipToResults, quizStateKey } = useQuizLocalState(sessionStateOptions);
7
7
  // Quiz API state
8
- const { quizApiState, dispatchApiState } = useQuizApiState(quizOptions, cioClient, quizLocalState, dispatchLocalState);
8
+ const { quizApiState, dispatchApiState } = useQuizApiState(quizOptions, cioClient, quizLocalState, skipToResults, dispatchLocalState);
9
9
  return {
10
10
  quizApiState,
11
11
  quizLocalState,
12
12
  dispatchApiState,
13
13
  dispatchLocalState,
14
14
  hasQuizStoredState,
15
+ skipToResults,
15
16
  quizStateKey,
16
17
  };
17
18
  };
@@ -2,7 +2,9 @@ import { useEffect, useReducer } from 'react';
2
2
  import { QuestionTypes, QuizAPIActionTypes, } from '../../components/CioQuiz/actions';
3
3
  import apiReducer, { initialState, } from '../../components/CioQuiz/quizApiReducer';
4
4
  import { getNextQuestion, getQuizResults } from '../../services';
5
- const useQuizApiState = (quizOptions, cioClient, quizLocalState, dispatchLocalState) => {
5
+ const useQuizApiState = (quizOptions, cioClient, quizLocalState, skipToResults, dispatchLocalState
6
+ // eslint-disable-next-line max-params
7
+ ) => {
6
8
  const [quizApiState, dispatchApiState] = useReducer(apiReducer, initialState);
7
9
  const { quizId, quizVersionId: quizVersionIdProp, resultsPageOptions } = quizOptions;
8
10
  useEffect(() => {
@@ -10,7 +12,7 @@ const useQuizApiState = (quizOptions, cioClient, quizLocalState, dispatchLocalSt
10
12
  dispatchApiState({
11
13
  type: QuizAPIActionTypes.SET_IS_LOADING,
12
14
  });
13
- if (quizLocalState.isLastAnswer) {
15
+ if (quizLocalState.isLastAnswer || skipToResults) {
14
16
  try {
15
17
  const quizResults = await getQuizResults(cioClient, quizId, {
16
18
  answers: quizLocalState.answers,
@@ -25,6 +27,10 @@ const useQuizApiState = (quizOptions, cioClient, quizLocalState, dispatchLocalSt
25
27
  quizResults,
26
28
  },
27
29
  });
30
+ if (!quizLocalState.isQuizCompleted)
31
+ dispatchLocalState({
32
+ type: QuestionTypes.Complete,
33
+ });
28
34
  }
29
35
  catch (error) {
30
36
  dispatchApiState({
@@ -2,9 +2,9 @@ import { useCallback, useEffect, useReducer } from 'react';
2
2
  import quizLocalReducer, { initialState } from '../../components/CioQuiz/quizLocalReducer';
3
3
  import { quizSessionStateKey } from '../../constants';
4
4
  import { getStateFromSessionStorage, logger } from '../../utils';
5
- const useQuizLocalState = (sessionStateKey) => {
5
+ const useQuizLocalState = (sessionStateOptions) => {
6
6
  const [quizLocalState, dispatch] = useReducer(quizLocalReducer, initialState);
7
- const quizStateKey = sessionStateKey || quizSessionStateKey;
7
+ const quizStateKey = sessionStateOptions?.sessionStateKey || quizSessionStateKey;
8
8
  useEffect(() => {
9
9
  // don't save state if initial state
10
10
  if (quizLocalState?.answers?.length) {
@@ -12,6 +12,8 @@ const useQuizLocalState = (sessionStateKey) => {
12
12
  }
13
13
  }, [quizLocalState, quizStateKey]);
14
14
  const hasQuizStoredState = () => getStateFromSessionStorage(quizStateKey) !== null;
15
+ const skipToResults = !!getStateFromSessionStorage(quizStateKey)?.isQuizCompleted &&
16
+ !sessionStateOptions?.showSessionModalOnResults;
15
17
  const dispatchLocalState = useCallback((action) => {
16
18
  logger(action);
17
19
  dispatch(action);
@@ -20,6 +22,7 @@ const useQuizLocalState = (sessionStateKey) => {
20
22
  quizLocalState,
21
23
  hasQuizStoredState,
22
24
  dispatchLocalState,
25
+ skipToResults,
23
26
  quizStateKey,
24
27
  };
25
28
  };
@@ -67,6 +67,7 @@ export const getMockState = (question) => ({
67
67
  requestState: RequestStates.Success,
68
68
  versionId: '',
69
69
  sessionId: '',
70
+ skipToResults: false,
70
71
  currentQuestion: {
71
72
  next_question: question,
72
73
  isFirstQuestion: false,
@@ -8,7 +8,8 @@ export declare enum QuestionTypes {
8
8
  Next = "next",
9
9
  Back = "back",
10
10
  Reset = "reset",
11
- Hydrate = "hydrate"
11
+ Hydrate = "hydrate",
12
+ Complete = "complete"
12
13
  }
13
14
  export interface QuestionAnswer<Value> {
14
15
  questionId: number;
@@ -23,7 +24,7 @@ interface Action<Type, Payload = {}> {
23
24
  payload?: Payload;
24
25
  }
25
26
  export type ActionAnswerInputQuestion = Action<QuestionTypes.OpenText, OpenTextQuestionPayload> | Action<QuestionTypes.SingleSelect, SelectQuestionPayload> | Action<QuestionTypes.MultipleSelect, SelectQuestionPayload> | Action<QuestionTypes.Cover, CoverQuestionPayload>;
26
- export type ActionAnswerQuestion = ActionAnswerInputQuestion | Action<QuestionTypes.Next, CurrentQuestion> | Action<QuestionTypes.Back, CurrentQuestion> | Action<QuestionTypes.Reset> | Action<QuestionTypes.Hydrate, Partial<QuizLocalReducerState>>;
27
+ export type ActionAnswerQuestion = ActionAnswerInputQuestion | Action<QuestionTypes.Next, CurrentQuestion> | Action<QuestionTypes.Back, CurrentQuestion> | Action<QuestionTypes.Reset> | Action<QuestionTypes.Complete> | Action<QuestionTypes.Hydrate, Partial<QuizLocalReducerState>>;
27
28
  export declare enum QuizAPIActionTypes {
28
29
  SET_IS_LOADING = 0,
29
30
  SET_IS_ERROR = 1,
@@ -5,6 +5,7 @@ export type QuizLocalReducerState = {
5
5
  answers: Answers;
6
6
  answerInputs: AnswerInputState;
7
7
  isLastAnswer: boolean;
8
+ isQuizCompleted: boolean;
8
9
  quizVersionId?: string;
9
10
  quizSessionId?: string;
10
11
  };