@constructor-io/constructorio-ui-quizzes 1.3.2 → 1.3.3

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 (32) hide show
  1. package/dist/constructorio-ui-quizzes-bundled.js +15 -15
  2. package/lib/cjs/components/CioQuiz/actions.js +1 -0
  3. package/lib/cjs/components/CioQuiz/index.js +16 -3
  4. package/lib/cjs/components/CioQuiz/quizApiReducer.js +5 -5
  5. package/lib/cjs/components/CioQuiz/quizLocalReducer.js +2 -0
  6. package/lib/cjs/components/SessionPromptModal/SessionPromptModal.js +24 -0
  7. package/lib/cjs/constants.js +3 -1
  8. package/lib/cjs/hooks/useQuiz.js +8 -5
  9. package/lib/cjs/hooks/useQuizApiState.js +16 -13
  10. package/lib/cjs/hooks/useQuizEvents/index.js +5 -1
  11. package/lib/cjs/hooks/useQuizLocalState.js +35 -1
  12. package/lib/mjs/components/CioQuiz/actions.js +1 -0
  13. package/lib/mjs/components/CioQuiz/index.js +16 -3
  14. package/lib/mjs/components/CioQuiz/quizApiReducer.js +0 -2
  15. package/lib/mjs/components/CioQuiz/quizLocalReducer.js +5 -0
  16. package/lib/mjs/components/SessionPromptModal/SessionPromptModal.js +20 -0
  17. package/lib/mjs/constants.js +2 -0
  18. package/lib/mjs/hooks/useQuiz.js +8 -5
  19. package/lib/mjs/hooks/useQuizApiState.js +17 -14
  20. package/lib/mjs/hooks/useQuizEvents/index.js +5 -1
  21. package/lib/mjs/hooks/useQuizLocalState.js +34 -3
  22. package/lib/styles.css +43 -0
  23. package/lib/types/components/CioQuiz/actions.d.ts +4 -2
  24. package/lib/types/components/CioQuiz/quizApiReducer.d.ts +0 -2
  25. package/lib/types/components/CioQuiz/quizLocalReducer.d.ts +2 -0
  26. package/lib/types/components/SessionPromptModal/SessionPromptModal.d.ts +9 -0
  27. package/lib/types/constants.d.ts +1 -0
  28. package/lib/types/hooks/useQuizApiState.d.ts +2 -1
  29. package/lib/types/hooks/useQuizEvents/index.d.ts +3 -0
  30. package/lib/types/hooks/useQuizLocalState.d.ts +6 -2
  31. package/lib/types/types.d.ts +11 -0
  32. package/package.json +1 -1
@@ -10,6 +10,7 @@ var QuestionTypes;
10
10
  QuestionTypes["MultipleSelect"] = "multiple";
11
11
  QuestionTypes["Back"] = "back";
12
12
  QuestionTypes["Reset"] = "reset";
13
+ QuestionTypes["Hydrate"] = "hydrate";
13
14
  })(QuestionTypes = exports.QuestionTypes || (exports.QuestionTypes = {}));
14
15
  // API actions
15
16
  var QuizAPIActionTypes;
@@ -1,16 +1,28 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
- const react_1 = tslib_1.__importDefault(require("react"));
4
+ const react_1 = tslib_1.__importStar(require("react"));
5
5
  const context_1 = tslib_1.__importDefault(require("./context"));
6
6
  const QuizQuestions_1 = tslib_1.__importDefault(require("../QuizQuestions"));
7
7
  const ResultContainer_1 = tslib_1.__importDefault(require("../ResultContainer/ResultContainer"));
8
8
  const constants_1 = require("../../constants");
9
9
  const Spinner_1 = tslib_1.__importDefault(require("../Spinner/Spinner"));
10
10
  const useQuiz_1 = tslib_1.__importDefault(require("../../hooks/useQuiz"));
11
+ const SessionPromptModal_1 = tslib_1.__importDefault(require("../SessionPromptModal/SessionPromptModal"));
11
12
  function CioQuiz(props) {
12
- const { cioClient, state, events: { nextQuestion, previousQuestion, resetQuiz, addToCart, resultClick }, } = (0, useQuiz_1.default)(props);
13
- const { resultsPageOptions } = props;
13
+ const { cioClient, state, events: { nextQuestion, previousQuestion, resetQuiz, addToCart, resultClick, hydrateQuiz, hasStoredState, resetStoredState, }, } = (0, useQuiz_1.default)(props);
14
+ const [showSessionPrompt, setShowSessionPrompt] = (0, react_1.useState)(false);
15
+ const { resultsPageOptions, sessionStateOptions } = props;
16
+ (0, react_1.useEffect)(() => {
17
+ // Respect showSessionModal if defined, else default to true.
18
+ if ((sessionStateOptions === null || sessionStateOptions === void 0 ? void 0 : sessionStateOptions.showSessionModal) !== undefined) {
19
+ setShowSessionPrompt((sessionStateOptions === null || sessionStateOptions === void 0 ? void 0 : sessionStateOptions.showSessionModal) && hasStoredState());
20
+ }
21
+ else {
22
+ setShowSessionPrompt(hasStoredState());
23
+ }
24
+ // eslint-disable-next-line react-hooks/exhaustive-deps
25
+ }, []);
14
26
  const contextValue = {
15
27
  cioClient,
16
28
  state,
@@ -27,6 +39,7 @@ function CioQuiz(props) {
27
39
  }
28
40
  if (state.quiz.requestState === constants_1.RequestStates.Success) {
29
41
  return (react_1.default.createElement("div", { className: 'cio-quiz' },
42
+ react_1.default.createElement(SessionPromptModal_1.default, { resetStoredState: resetStoredState, continueSession: hydrateQuiz, showSessionPrompt: showSessionPrompt, setShowSessionPrompt: setShowSessionPrompt }),
30
43
  react_1.default.createElement(context_1.default.Provider, { value: contextValue },
31
44
  state.quiz.results && react_1.default.createElement(ResultContainer_1.default, { options: resultsPageOptions }),
32
45
  state.quiz.currentQuestion && react_1.default.createElement(QuizQuestions_1.default, null))));
@@ -8,20 +8,20 @@ exports.initialState = {
8
8
  quizRequestState: constants_1.RequestStates.Stale,
9
9
  };
10
10
  function apiReducer(state, action) {
11
- var _a, _b, _c, _d, _e, _f, _g, _h;
11
+ var _a, _b, _c, _d, _e, _f;
12
12
  switch (action.type) {
13
13
  case actions_1.QuizAPIActionTypes.SET_IS_LOADING:
14
14
  return Object.assign(Object.assign({}, state), { quizRequestState: constants_1.RequestStates.Loading });
15
15
  case actions_1.QuizAPIActionTypes.SET_IS_ERROR:
16
16
  return Object.assign(Object.assign({}, state), { quizRequestState: constants_1.RequestStates.Error, quizResults: undefined });
17
17
  case actions_1.QuizAPIActionTypes.SET_CURRENT_QUESTION:
18
- return Object.assign(Object.assign(Object.assign({}, state), { quizRequestState: constants_1.RequestStates.Success, quizVersionId: (_a = action.payload) === null || _a === void 0 ? void 0 : _a.quizVersionId, quizSessionId: (_b = action.payload) === null || _b === void 0 ? void 0 : _b.quizSessionId, quizCurrentQuestion: (_c = action.payload) === null || _c === void 0 ? void 0 : _c.quizCurrentQuestion, quizResults: undefined }), (!state.quizCurrentQuestion && {
19
- quizFirstQuestion: (_d = action.payload) === null || _d === void 0 ? void 0 : _d.quizCurrentQuestion,
18
+ return Object.assign(Object.assign(Object.assign({}, state), { quizRequestState: constants_1.RequestStates.Success, quizCurrentQuestion: (_a = action.payload) === null || _a === void 0 ? void 0 : _a.quizCurrentQuestion, quizResults: undefined }), (!state.quizCurrentQuestion && {
19
+ quizFirstQuestion: (_b = action.payload) === null || _b === void 0 ? void 0 : _b.quizCurrentQuestion,
20
20
  }));
21
21
  case actions_1.QuizAPIActionTypes.SET_QUIZ_RESULTS: {
22
- const filterExpression = ((_g = (_f = (_e = action.payload) === null || _e === void 0 ? void 0 : _e.quizResults) === null || _f === void 0 ? void 0 : _f.request) === null || _g === void 0 ? void 0 : _g.collection_filter_expression) || null;
22
+ const filterExpression = ((_e = (_d = (_c = action.payload) === null || _c === void 0 ? void 0 : _c.quizResults) === null || _d === void 0 ? void 0 : _d.request) === null || _e === void 0 ? void 0 : _e.collection_filter_expression) || null;
23
23
  const quizResultsFilters = [...new Set((0, utils_1.getFilterValuesFromExpression)(filterExpression))];
24
- return Object.assign(Object.assign({}, state), { quizRequestState: constants_1.RequestStates.Success, quizResults: (_h = action.payload) === null || _h === void 0 ? void 0 : _h.quizResults, quizResultsFilters, quizCurrentQuestion: undefined });
24
+ return Object.assign(Object.assign({}, state), { quizRequestState: constants_1.RequestStates.Success, quizResults: (_f = action.payload) === null || _f === void 0 ? void 0 : _f.quizResults, quizResultsFilters, quizCurrentQuestion: undefined });
25
25
  }
26
26
  case actions_1.QuizAPIActionTypes.RESET_QUIZ:
27
27
  return exports.initialState;
@@ -25,6 +25,8 @@ function quizLocalReducer(state, action) {
25
25
  return Object.assign(Object.assign({}, state), { answers: [...state.answers.slice(0, -1)], isLastAnswer: false });
26
26
  case actions_1.QuestionTypes.Reset:
27
27
  return Object.assign({}, exports.initialState);
28
+ case actions_1.QuestionTypes.Hydrate:
29
+ return Object.assign(Object.assign({}, state), action.payload);
28
30
  default:
29
31
  return state;
30
32
  }
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const react_1 = tslib_1.__importDefault(require("react"));
5
+ const CTAButton_1 = tslib_1.__importDefault(require("../CTAButton/CTAButton"));
6
+ function SessionPromptModal({ continueSession, resetStoredState, setShowSessionPrompt, showSessionPrompt, }) {
7
+ const onNoClickHandler = () => {
8
+ resetStoredState();
9
+ setShowSessionPrompt(false);
10
+ };
11
+ if (showSessionPrompt)
12
+ return (react_1.default.createElement("div", { className: 'cio-session-prompt-modal', role: 'presentation', onClick: onNoClickHandler },
13
+ react_1.default.createElement("div", { className: 'cio-session-prompt-container' },
14
+ react_1.default.createElement("div", { className: 'cio-session-prompt-content', role: 'presentation', onClick: (e) => e.stopPropagation() },
15
+ react_1.default.createElement("div", null, "Do you want to continue where you left off?"),
16
+ react_1.default.createElement("div", { className: 'cio-session-prompt-controls-container' },
17
+ react_1.default.createElement(CTAButton_1.default, { ctaText: 'No', type: 'button', onClick: onNoClickHandler }),
18
+ react_1.default.createElement(CTAButton_1.default, { type: 'button', ctaText: 'Yes', onClick: () => {
19
+ continueSession();
20
+ setShowSessionPrompt(false);
21
+ } }))))));
22
+ return null;
23
+ }
24
+ exports.default = SessionPromptModal;
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RequestStates = exports.smallContainerDescription = exports.cioJsClientDescription = exports.basicDescription = exports.componentDescription = exports.quizId = exports.apiKey = void 0;
3
+ exports.RequestStates = exports.smallContainerDescription = exports.cioJsClientDescription = exports.basicDescription = exports.componentDescription = exports.quizSessionStateKey = exports.quizId = exports.apiKey = void 0;
4
4
  // Autocomplete key index
5
5
  exports.apiKey = 'key_wJSdZSiesX5hiVLt';
6
6
  exports.quizId = 'coffee-quiz';
7
+ // Session Storage default key
8
+ exports.quizSessionStateKey = 'constructorIOQuizState';
7
9
  /// //////////////////////////////
8
10
  // Storybook Folder Descriptions
9
11
  /// //////////////////////////////
@@ -6,15 +6,15 @@ const useConsoleErrors_1 = tslib_1.__importDefault(require("./useConsoleErrors")
6
6
  const useQuizApiState_1 = tslib_1.__importDefault(require("./useQuizApiState"));
7
7
  const useQuizEvents_1 = tslib_1.__importDefault(require("./useQuizEvents"));
8
8
  const useQuizLocalState_1 = tslib_1.__importDefault(require("./useQuizLocalState"));
9
- const useQuiz = ({ quizId, apiKey, cioJsClient, quizVersionId, resultsPageOptions }) => {
9
+ const useQuiz = ({ quizId, apiKey, cioJsClient, quizVersionId, resultsPageOptions, sessionStateOptions, }) => {
10
10
  // Log console errors for required parameters quizId and resultsPageOptions
11
11
  (0, useConsoleErrors_1.default)(quizId, resultsPageOptions);
12
12
  // Quiz Local state
13
- const { quizLocalState, resetQuizLocalState, dispatchLocalState } = (0, useQuizLocalState_1.default)();
13
+ const { quizLocalState, resetQuizLocalState, dispatchLocalState, hydrateQuizLocalState, hasQuizStoredState, resetQuizStoredState, } = (0, useQuizLocalState_1.default)(sessionStateOptions === null || sessionStateOptions === void 0 ? void 0 : sessionStateOptions.sessionStateKey);
14
14
  // Quiz Cio Client
15
15
  const cioClient = (0, useCioClient_1.default)({ apiKey, cioJsClient });
16
16
  // Quiz API state
17
- const { isFirstQuestion, quizApiState, resetQuizApiState } = (0, useQuizApiState_1.default)(quizId, quizLocalState, resultsPageOptions, quizVersionId, cioClient);
17
+ const { isFirstQuestion, quizApiState, resetQuizApiState } = (0, useQuizApiState_1.default)(quizId, quizLocalState, dispatchLocalState, resultsPageOptions, quizVersionId, cioClient);
18
18
  // Quiz callback events
19
19
  const quizEvents = (0, useQuizEvents_1.default)({
20
20
  cioClient,
@@ -23,6 +23,9 @@ const useQuiz = ({ quizId, apiKey, cioJsClient, quizVersionId, resultsPageOption
23
23
  dispatchLocalState,
24
24
  resetQuizApiState,
25
25
  resetQuizLocalState,
26
+ hydrateQuizLocalState,
27
+ resetQuizStoredState,
28
+ hasQuizStoredState,
26
29
  });
27
30
  return {
28
31
  cioClient,
@@ -33,8 +36,8 @@ const useQuiz = ({ quizId, apiKey, cioJsClient, quizVersionId, resultsPageOption
33
36
  },
34
37
  quiz: {
35
38
  requestState: quizApiState.quizRequestState,
36
- versionId: quizApiState.quizVersionId,
37
- sessionId: quizApiState.quizSessionId,
39
+ versionId: quizLocalState.quizVersionId,
40
+ sessionId: quizLocalState.quizSessionId,
38
41
  firstQuestion: quizApiState.quizFirstQuestion,
39
42
  currentQuestion: quizApiState.quizCurrentQuestion,
40
43
  results: quizApiState.quizResults,
@@ -5,7 +5,7 @@ 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 useFetchQuiz = (quizId, quizLocalState, resultsPageOptions, quizVersionIdProp, cioClient) => {
8
+ const useFetchQuiz = (quizId, quizLocalState, dispatchLocalState, resultsPageOptions, quizVersionIdProp, cioClient) => {
9
9
  var _a, _b;
10
10
  const [quizApiState, dispatch] = (0, react_1.useReducer)(quizApiReducer_1.default, quizApiReducer_1.initialState);
11
11
  const firstQuestionId = (_a = quizApiState.quizFirstQuestion) === null || _a === void 0 ? void 0 : _a.next_question.id;
@@ -21,8 +21,8 @@ const useFetchQuiz = (quizId, quizLocalState, resultsPageOptions, quizVersionIdP
21
21
  const quizResults = yield (0, services_1.getQuizResults)(cioClient, quizId, {
22
22
  answers: quizLocalState.answers,
23
23
  resultsPerPage: resultsPageOptions === null || resultsPageOptions === void 0 ? void 0 : resultsPageOptions.numResultsToDisplay,
24
- quizVersionId: quizApiState.quizVersionId,
25
- quizSessionId: quizApiState.quizSessionId,
24
+ quizVersionId: quizLocalState.quizVersionId,
25
+ quizSessionId: quizLocalState.quizSessionId,
26
26
  });
27
27
  // Set quiz results state
28
28
  dispatch({
@@ -40,25 +40,28 @@ const useFetchQuiz = (quizId, quizLocalState, resultsPageOptions, quizVersionIdP
40
40
  }
41
41
  else {
42
42
  try {
43
- let quizVersionId = quizApiState.quizVersionId || quizVersionIdProp;
44
- let { quizSessionId } = quizApiState;
43
+ const quizVersionId = quizLocalState.quizVersionId || quizVersionIdProp;
44
+ const { quizSessionId } = quizLocalState;
45
45
  const questionResult = yield (0, services_1.nextQuestion)(cioClient, quizId, {
46
46
  answers: quizLocalState.answers,
47
47
  quizVersionId,
48
48
  quizSessionId,
49
49
  });
50
- if (!quizVersionId && (questionResult === null || questionResult === void 0 ? void 0 : questionResult.quiz_version_id)) {
51
- quizVersionId = questionResult.quiz_version_id;
52
- }
53
- if (!quizSessionId && (questionResult === null || questionResult === void 0 ? void 0 : questionResult.quiz_session_id)) {
54
- quizSessionId = questionResult.quiz_session_id;
50
+ // Update quizSessionId, quizVersionId
51
+ if ((!quizSessionId && (questionResult === null || questionResult === void 0 ? void 0 : questionResult.quiz_session_id)) ||
52
+ (!quizVersionId && questionResult.quiz_version_id)) {
53
+ dispatchLocalState({
54
+ type: actions_1.QuestionTypes.Hydrate,
55
+ payload: {
56
+ quizVersionId: questionResult === null || questionResult === void 0 ? void 0 : questionResult.quiz_version_id,
57
+ quizSessionId: questionResult === null || questionResult === void 0 ? void 0 : questionResult.quiz_session_id,
58
+ },
59
+ });
55
60
  }
56
61
  // Set current question state
57
62
  dispatch({
58
63
  type: actions_1.QuizAPIActionTypes.SET_CURRENT_QUESTION,
59
64
  payload: {
60
- quizSessionId,
61
- quizVersionId,
62
65
  quizCurrentQuestion: questionResult,
63
66
  },
64
67
  });
@@ -74,7 +77,7 @@ const useFetchQuiz = (quizId, quizLocalState, resultsPageOptions, quizVersionIdP
74
77
  }, [
75
78
  cioClient,
76
79
  quizId,
77
- quizLocalState,
80
+ quizLocalState.answers,
78
81
  quizLocalState.isLastAnswer,
79
82
  resultsPageOptions === null || resultsPageOptions === void 0 ? void 0 : resultsPageOptions.numResultsToDisplay,
80
83
  ]);
@@ -7,7 +7,7 @@ const useQuizAddToCart_1 = tslib_1.__importDefault(require("./useQuizAddToCart")
7
7
  const useQuizNextClick_1 = tslib_1.__importDefault(require("./useQuizNextClick"));
8
8
  const useQuizBackClick_1 = tslib_1.__importDefault(require("./useQuizBackClick"));
9
9
  const useQuizEvents = (options) => {
10
- const { cioClient, quizApiState, resultsPageOptions, dispatchLocalState, resetQuizApiState, resetQuizLocalState, } = options;
10
+ const { cioClient, quizApiState, resultsPageOptions, dispatchLocalState, resetQuizApiState, resetQuizLocalState, hydrateQuizLocalState, resetQuizStoredState, hasQuizStoredState, } = options;
11
11
  const { onAddToCartClick, onQuizResultClick, onQuizResultsLoaded } = resultsPageOptions;
12
12
  // Quiz Next button click
13
13
  const nextQuestion = (0, useQuizNextClick_1.default)(quizApiState, dispatchLocalState);
@@ -23,6 +23,7 @@ const useQuizEvents = (options) => {
23
23
  if (quizApiState.quizResults) {
24
24
  resetQuizApiState();
25
25
  resetQuizLocalState();
26
+ resetQuizStoredState();
26
27
  }
27
28
  };
28
29
  return {
@@ -31,6 +32,9 @@ const useQuizEvents = (options) => {
31
32
  nextQuestion,
32
33
  previousQuestion,
33
34
  resetQuiz,
35
+ hydrateQuiz: hydrateQuizLocalState,
36
+ hasStoredState: hasQuizStoredState,
37
+ resetStoredState: resetQuizStoredState,
34
38
  };
35
39
  };
36
40
  exports.default = useQuizEvents;
@@ -4,16 +4,50 @@ const tslib_1 = require("tslib");
4
4
  const react_1 = require("react");
5
5
  const actions_1 = require("../components/CioQuiz/actions");
6
6
  const quizLocalReducer_1 = tslib_1.__importStar(require("../components/CioQuiz/quizLocalReducer"));
7
- const useQuizLocalState = () => {
7
+ const constants_1 = require("../constants");
8
+ const useQuizLocalState = (sessionStateKey) => {
8
9
  const [quizLocalState, dispatch] = (0, react_1.useReducer)(quizLocalReducer_1.default, quizLocalReducer_1.initialState);
10
+ const quizStateKey = sessionStateKey || constants_1.quizSessionStateKey;
11
+ const getStateFromSessionStorage = () => {
12
+ var _a;
13
+ const state = (_a = window === null || window === void 0 ? void 0 : window.sessionStorage) === null || _a === void 0 ? void 0 : _a.getItem(quizStateKey);
14
+ if (state) {
15
+ return JSON.parse(state);
16
+ }
17
+ return null;
18
+ };
19
+ (0, react_1.useEffect)(() => {
20
+ var _a, _b;
21
+ // don't save state if initial state
22
+ if ((_a = quizLocalState === null || quizLocalState === void 0 ? void 0 : quizLocalState.answers) === null || _a === void 0 ? void 0 : _a.length) {
23
+ (_b = window === null || window === void 0 ? void 0 : window.sessionStorage) === null || _b === void 0 ? void 0 : _b.setItem(quizStateKey, JSON.stringify(quizLocalState));
24
+ }
25
+ }, [quizLocalState, quizStateKey]);
9
26
  const resetQuizLocalState = () => {
10
27
  dispatch({
11
28
  type: actions_1.QuestionTypes.Reset,
12
29
  });
13
30
  };
31
+ const hydrateQuizLocalState = () => {
32
+ const quizState = getStateFromSessionStorage();
33
+ if (quizState) {
34
+ dispatch({
35
+ type: actions_1.QuestionTypes.Hydrate,
36
+ payload: quizState,
37
+ });
38
+ }
39
+ };
40
+ const resetQuizStoredState = () => {
41
+ var _a;
42
+ (_a = window === null || window === void 0 ? void 0 : window.sessionStorage) === null || _a === void 0 ? void 0 : _a.removeItem(quizStateKey);
43
+ };
44
+ const hasQuizStoredState = () => getStateFromSessionStorage() !== null;
14
45
  return {
15
46
  quizLocalState,
16
47
  resetQuizLocalState,
48
+ hydrateQuizLocalState,
49
+ hasQuizStoredState,
50
+ resetQuizStoredState,
17
51
  dispatchLocalState: dispatch,
18
52
  };
19
53
  };
@@ -7,6 +7,7 @@ export var QuestionTypes;
7
7
  QuestionTypes["MultipleSelect"] = "multiple";
8
8
  QuestionTypes["Back"] = "back";
9
9
  QuestionTypes["Reset"] = "reset";
10
+ QuestionTypes["Hydrate"] = "hydrate";
10
11
  })(QuestionTypes || (QuestionTypes = {}));
11
12
  // API actions
12
13
  export var QuizAPIActionTypes;
@@ -1,13 +1,25 @@
1
- import React from 'react';
1
+ import React, { useEffect, useState } from 'react';
2
2
  import QuizContext from './context';
3
3
  import QuizQuestions from '../QuizQuestions';
4
4
  import ResultContainer from '../ResultContainer/ResultContainer';
5
5
  import { RequestStates } from '../../constants';
6
6
  import Spinner from '../Spinner/Spinner';
7
7
  import useQuiz from '../../hooks/useQuiz';
8
+ import SessionPromptModal from '../SessionPromptModal/SessionPromptModal';
8
9
  export default function CioQuiz(props) {
9
- const { cioClient, state, events: { nextQuestion, previousQuestion, resetQuiz, addToCart, resultClick }, } = useQuiz(props);
10
- const { resultsPageOptions } = props;
10
+ const { cioClient, state, events: { nextQuestion, previousQuestion, resetQuiz, addToCart, resultClick, hydrateQuiz, hasStoredState, resetStoredState, }, } = useQuiz(props);
11
+ const [showSessionPrompt, setShowSessionPrompt] = useState(false);
12
+ const { resultsPageOptions, sessionStateOptions } = props;
13
+ useEffect(() => {
14
+ // Respect showSessionModal if defined, else default to true.
15
+ if (sessionStateOptions?.showSessionModal !== undefined) {
16
+ setShowSessionPrompt(sessionStateOptions?.showSessionModal && hasStoredState());
17
+ }
18
+ else {
19
+ setShowSessionPrompt(hasStoredState());
20
+ }
21
+ // eslint-disable-next-line react-hooks/exhaustive-deps
22
+ }, []);
11
23
  const contextValue = {
12
24
  cioClient,
13
25
  state,
@@ -24,6 +36,7 @@ export default function CioQuiz(props) {
24
36
  }
25
37
  if (state.quiz.requestState === RequestStates.Success) {
26
38
  return (React.createElement("div", { className: 'cio-quiz' },
39
+ React.createElement(SessionPromptModal, { resetStoredState: resetStoredState, continueSession: hydrateQuiz, showSessionPrompt: showSessionPrompt, setShowSessionPrompt: setShowSessionPrompt }),
27
40
  React.createElement(QuizContext.Provider, { value: contextValue },
28
41
  state.quiz.results && React.createElement(ResultContainer, { options: resultsPageOptions }),
29
42
  state.quiz.currentQuestion && React.createElement(QuizQuestions, null))));
@@ -21,8 +21,6 @@ export default function apiReducer(state, action) {
21
21
  return {
22
22
  ...state,
23
23
  quizRequestState: RequestStates.Success,
24
- quizVersionId: action.payload?.quizVersionId,
25
- quizSessionId: action.payload?.quizSessionId,
26
24
  quizCurrentQuestion: action.payload?.quizCurrentQuestion,
27
25
  quizResults: undefined,
28
26
  // If no current question set first question value
@@ -49,6 +49,11 @@ export default function quizLocalReducer(state, action) {
49
49
  return {
50
50
  ...initialState,
51
51
  };
52
+ case QuestionTypes.Hydrate:
53
+ return {
54
+ ...state,
55
+ ...action.payload,
56
+ };
52
57
  default:
53
58
  return state;
54
59
  }
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import CTAButton from '../CTAButton/CTAButton';
3
+ export default function SessionPromptModal({ continueSession, resetStoredState, setShowSessionPrompt, showSessionPrompt, }) {
4
+ const onNoClickHandler = () => {
5
+ resetStoredState();
6
+ setShowSessionPrompt(false);
7
+ };
8
+ if (showSessionPrompt)
9
+ return (React.createElement("div", { className: 'cio-session-prompt-modal', role: 'presentation', onClick: onNoClickHandler },
10
+ React.createElement("div", { className: 'cio-session-prompt-container' },
11
+ React.createElement("div", { className: 'cio-session-prompt-content', role: 'presentation', onClick: (e) => e.stopPropagation() },
12
+ React.createElement("div", null, "Do you want to continue where you left off?"),
13
+ React.createElement("div", { className: 'cio-session-prompt-controls-container' },
14
+ React.createElement(CTAButton, { ctaText: 'No', type: 'button', onClick: onNoClickHandler }),
15
+ React.createElement(CTAButton, { type: 'button', ctaText: 'Yes', onClick: () => {
16
+ continueSession();
17
+ setShowSessionPrompt(false);
18
+ } }))))));
19
+ return null;
20
+ }
@@ -1,6 +1,8 @@
1
1
  // Autocomplete key index
2
2
  export const apiKey = 'key_wJSdZSiesX5hiVLt';
3
3
  export const quizId = 'coffee-quiz';
4
+ // Session Storage default key
5
+ export const quizSessionStateKey = 'constructorIOQuizState';
4
6
  /// //////////////////////////////
5
7
  // Storybook Folder Descriptions
6
8
  /// //////////////////////////////
@@ -3,15 +3,15 @@ import useConsoleErrors from './useConsoleErrors';
3
3
  import useQuizApiState from './useQuizApiState';
4
4
  import useQuizEvents from './useQuizEvents';
5
5
  import useQuizLocalState from './useQuizLocalState';
6
- const useQuiz = ({ quizId, apiKey, cioJsClient, quizVersionId, resultsPageOptions }) => {
6
+ const useQuiz = ({ quizId, apiKey, cioJsClient, quizVersionId, resultsPageOptions, sessionStateOptions, }) => {
7
7
  // Log console errors for required parameters quizId and resultsPageOptions
8
8
  useConsoleErrors(quizId, resultsPageOptions);
9
9
  // Quiz Local state
10
- const { quizLocalState, resetQuizLocalState, dispatchLocalState } = useQuizLocalState();
10
+ const { quizLocalState, resetQuizLocalState, dispatchLocalState, hydrateQuizLocalState, hasQuizStoredState, resetQuizStoredState, } = useQuizLocalState(sessionStateOptions?.sessionStateKey);
11
11
  // Quiz Cio Client
12
12
  const cioClient = useCioClient({ apiKey, cioJsClient });
13
13
  // Quiz API state
14
- const { isFirstQuestion, quizApiState, resetQuizApiState } = useQuizApiState(quizId, quizLocalState, resultsPageOptions, quizVersionId, cioClient);
14
+ const { isFirstQuestion, quizApiState, resetQuizApiState } = useQuizApiState(quizId, quizLocalState, dispatchLocalState, resultsPageOptions, quizVersionId, cioClient);
15
15
  // Quiz callback events
16
16
  const quizEvents = useQuizEvents({
17
17
  cioClient,
@@ -20,6 +20,9 @@ const useQuiz = ({ quizId, apiKey, cioJsClient, quizVersionId, resultsPageOption
20
20
  dispatchLocalState,
21
21
  resetQuizApiState,
22
22
  resetQuizLocalState,
23
+ hydrateQuizLocalState,
24
+ resetQuizStoredState,
25
+ hasQuizStoredState,
23
26
  });
24
27
  return {
25
28
  cioClient,
@@ -30,8 +33,8 @@ const useQuiz = ({ quizId, apiKey, cioJsClient, quizVersionId, resultsPageOption
30
33
  },
31
34
  quiz: {
32
35
  requestState: quizApiState.quizRequestState,
33
- versionId: quizApiState.quizVersionId,
34
- sessionId: quizApiState.quizSessionId,
36
+ versionId: quizLocalState.quizVersionId,
37
+ sessionId: quizLocalState.quizSessionId,
35
38
  firstQuestion: quizApiState.quizFirstQuestion,
36
39
  currentQuestion: quizApiState.quizCurrentQuestion,
37
40
  results: quizApiState.quizResults,
@@ -1,8 +1,8 @@
1
1
  import { useEffect, useReducer } from 'react';
2
- import { QuizAPIActionTypes } from '../components/CioQuiz/actions';
2
+ import { QuestionTypes, QuizAPIActionTypes, } from '../components/CioQuiz/actions';
3
3
  import apiReducer, { initialState } from '../components/CioQuiz/quizApiReducer';
4
4
  import { nextQuestion, getQuizResults } from '../services';
5
- const useFetchQuiz = (quizId, quizLocalState, resultsPageOptions, quizVersionIdProp, cioClient) => {
5
+ const useFetchQuiz = (quizId, quizLocalState, dispatchLocalState, resultsPageOptions, quizVersionIdProp, cioClient) => {
6
6
  const [quizApiState, dispatch] = useReducer(apiReducer, initialState);
7
7
  const firstQuestionId = quizApiState.quizFirstQuestion?.next_question.id;
8
8
  const currentQuestionId = quizApiState.quizCurrentQuestion?.next_question.id;
@@ -17,8 +17,8 @@ const useFetchQuiz = (quizId, quizLocalState, resultsPageOptions, quizVersionIdP
17
17
  const quizResults = await getQuizResults(cioClient, quizId, {
18
18
  answers: quizLocalState.answers,
19
19
  resultsPerPage: resultsPageOptions?.numResultsToDisplay,
20
- quizVersionId: quizApiState.quizVersionId,
21
- quizSessionId: quizApiState.quizSessionId,
20
+ quizVersionId: quizLocalState.quizVersionId,
21
+ quizSessionId: quizLocalState.quizSessionId,
22
22
  });
23
23
  // Set quiz results state
24
24
  dispatch({
@@ -36,25 +36,28 @@ const useFetchQuiz = (quizId, quizLocalState, resultsPageOptions, quizVersionIdP
36
36
  }
37
37
  else {
38
38
  try {
39
- let quizVersionId = quizApiState.quizVersionId || quizVersionIdProp;
40
- let { quizSessionId } = quizApiState;
39
+ const quizVersionId = quizLocalState.quizVersionId || quizVersionIdProp;
40
+ const { quizSessionId } = quizLocalState;
41
41
  const questionResult = await nextQuestion(cioClient, quizId, {
42
42
  answers: quizLocalState.answers,
43
43
  quizVersionId,
44
44
  quizSessionId,
45
45
  });
46
- if (!quizVersionId && questionResult?.quiz_version_id) {
47
- quizVersionId = questionResult.quiz_version_id;
48
- }
49
- if (!quizSessionId && questionResult?.quiz_session_id) {
50
- quizSessionId = questionResult.quiz_session_id;
46
+ // Update quizSessionId, quizVersionId
47
+ if ((!quizSessionId && questionResult?.quiz_session_id) ||
48
+ (!quizVersionId && questionResult.quiz_version_id)) {
49
+ dispatchLocalState({
50
+ type: QuestionTypes.Hydrate,
51
+ payload: {
52
+ quizVersionId: questionResult?.quiz_version_id,
53
+ quizSessionId: questionResult?.quiz_session_id,
54
+ },
55
+ });
51
56
  }
52
57
  // Set current question state
53
58
  dispatch({
54
59
  type: QuizAPIActionTypes.SET_CURRENT_QUESTION,
55
60
  payload: {
56
- quizSessionId,
57
- quizVersionId,
58
61
  quizCurrentQuestion: questionResult,
59
62
  },
60
63
  });
@@ -70,7 +73,7 @@ const useFetchQuiz = (quizId, quizLocalState, resultsPageOptions, quizVersionIdP
70
73
  }, [
71
74
  cioClient,
72
75
  quizId,
73
- quizLocalState,
76
+ quizLocalState.answers,
74
77
  quizLocalState.isLastAnswer,
75
78
  resultsPageOptions?.numResultsToDisplay,
76
79
  ]);
@@ -4,7 +4,7 @@ import useQuizAddToCart from './useQuizAddToCart';
4
4
  import useQuizNextClick from './useQuizNextClick';
5
5
  import useQuizBackClick from './useQuizBackClick';
6
6
  const useQuizEvents = (options) => {
7
- const { cioClient, quizApiState, resultsPageOptions, dispatchLocalState, resetQuizApiState, resetQuizLocalState, } = options;
7
+ const { cioClient, quizApiState, resultsPageOptions, dispatchLocalState, resetQuizApiState, resetQuizLocalState, hydrateQuizLocalState, resetQuizStoredState, hasQuizStoredState, } = options;
8
8
  const { onAddToCartClick, onQuizResultClick, onQuizResultsLoaded } = resultsPageOptions;
9
9
  // Quiz Next button click
10
10
  const nextQuestion = useQuizNextClick(quizApiState, dispatchLocalState);
@@ -20,6 +20,7 @@ const useQuizEvents = (options) => {
20
20
  if (quizApiState.quizResults) {
21
21
  resetQuizApiState();
22
22
  resetQuizLocalState();
23
+ resetQuizStoredState();
23
24
  }
24
25
  };
25
26
  return {
@@ -28,6 +29,9 @@ const useQuizEvents = (options) => {
28
29
  nextQuestion,
29
30
  previousQuestion,
30
31
  resetQuiz,
32
+ hydrateQuiz: hydrateQuizLocalState,
33
+ hasStoredState: hasQuizStoredState,
34
+ resetStoredState: resetQuizStoredState,
31
35
  };
32
36
  };
33
37
  export default useQuizEvents;
@@ -1,16 +1,47 @@
1
- import { useReducer } from 'react';
1
+ import { useEffect, useReducer } from 'react';
2
2
  import { QuestionTypes } from '../components/CioQuiz/actions';
3
- import quizLocalReducer, { initialState } from '../components/CioQuiz/quizLocalReducer';
4
- const useQuizLocalState = () => {
3
+ import quizLocalReducer, { initialState, } from '../components/CioQuiz/quizLocalReducer';
4
+ import { quizSessionStateKey } from '../constants';
5
+ const useQuizLocalState = (sessionStateKey) => {
5
6
  const [quizLocalState, dispatch] = useReducer(quizLocalReducer, initialState);
7
+ const quizStateKey = sessionStateKey || quizSessionStateKey;
8
+ const getStateFromSessionStorage = () => {
9
+ const state = window?.sessionStorage?.getItem(quizStateKey);
10
+ if (state) {
11
+ return JSON.parse(state);
12
+ }
13
+ return null;
14
+ };
15
+ useEffect(() => {
16
+ // don't save state if initial state
17
+ if (quizLocalState?.answers?.length) {
18
+ window?.sessionStorage?.setItem(quizStateKey, JSON.stringify(quizLocalState));
19
+ }
20
+ }, [quizLocalState, quizStateKey]);
6
21
  const resetQuizLocalState = () => {
7
22
  dispatch({
8
23
  type: QuestionTypes.Reset,
9
24
  });
10
25
  };
26
+ const hydrateQuizLocalState = () => {
27
+ const quizState = getStateFromSessionStorage();
28
+ if (quizState) {
29
+ dispatch({
30
+ type: QuestionTypes.Hydrate,
31
+ payload: quizState,
32
+ });
33
+ }
34
+ };
35
+ const resetQuizStoredState = () => {
36
+ window?.sessionStorage?.removeItem(quizStateKey);
37
+ };
38
+ const hasQuizStoredState = () => getStateFromSessionStorage() !== null;
11
39
  return {
12
40
  quizLocalState,
13
41
  resetQuizLocalState,
42
+ hydrateQuizLocalState,
43
+ hasQuizStoredState,
44
+ resetQuizStoredState,
14
45
  dispatchLocalState: dispatch,
15
46
  };
16
47
  };