@constructor-io/constructorio-ui-quizzes 1.2.3 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/constructorio-ui-quizzes-bundled.js +18 -18
- package/lib/cjs/components/CioQuiz/actions.js +11 -1
- package/lib/cjs/components/CioQuiz/index.js +15 -103
- package/lib/cjs/components/CioQuiz/quizApiReducer.js +32 -0
- package/lib/cjs/components/CioQuiz/{reducer.js → quizLocalReducer.js} +2 -2
- package/lib/cjs/components/CoverTypeQuestion/CoverTypeQuestion.js +6 -12
- package/lib/cjs/components/OpenTextTypeQuestion/OpenTextTypeQuestion.js +12 -18
- package/lib/cjs/components/QuizQuestions/index.js +6 -3
- package/lib/cjs/components/ResultCard/ResultCard.js +10 -30
- package/lib/cjs/components/ResultContainer/ResultContainer.js +8 -31
- package/lib/cjs/components/ResultCtaButton/ResultCtaButton.js +7 -26
- package/lib/cjs/components/ResultFilters/ResultFilters.js +6 -23
- package/lib/cjs/components/Results/Results.js +5 -5
- package/lib/cjs/components/SelectTypeQuestion/SelectTypeQuestion.js +16 -23
- package/lib/cjs/components/ZeroResults/ZeroResults.js +2 -2
- package/lib/cjs/constants.js +5 -3
- package/lib/cjs/hooks/useCioClient.js +4 -3
- package/lib/cjs/hooks/useConsoleErrors.js +20 -0
- package/lib/cjs/hooks/useQuiz.js +48 -0
- package/lib/cjs/hooks/useQuizApiState.js +91 -0
- package/lib/cjs/hooks/useQuizEvents/index.js +36 -0
- package/lib/cjs/hooks/useQuizEvents/useQuizAddToCart.js +20 -0
- package/lib/cjs/hooks/useQuizEvents/useQuizBackClick.js +13 -0
- package/lib/cjs/hooks/useQuizEvents/useQuizNextClick.js +48 -0
- package/lib/cjs/hooks/useQuizEvents/useQuizResultClick.js +19 -0
- package/lib/cjs/hooks/useQuizEvents/useQuizResultsLoaded.js +22 -0
- package/lib/cjs/hooks/useQuizLocalState.js +20 -0
- package/lib/cjs/services/index.js +72 -0
- package/lib/cjs/utils.js +42 -21
- package/lib/mjs/components/CioQuiz/actions.js +10 -0
- package/lib/mjs/components/CioQuiz/index.js +15 -103
- package/lib/mjs/components/CioQuiz/quizApiReducer.js +49 -0
- package/lib/mjs/components/CioQuiz/{reducer.js → quizLocalReducer.js} +1 -1
- package/lib/mjs/components/CoverTypeQuestion/CoverTypeQuestion.js +6 -12
- package/lib/mjs/components/OpenTextTypeQuestion/OpenTextTypeQuestion.js +11 -17
- package/lib/mjs/components/QuizQuestions/index.js +5 -3
- package/lib/mjs/components/ResultCard/ResultCard.js +10 -29
- package/lib/mjs/components/ResultContainer/ResultContainer.js +8 -31
- package/lib/mjs/components/ResultCtaButton/ResultCtaButton.js +7 -25
- package/lib/mjs/components/ResultFilters/ResultFilters.js +5 -23
- package/lib/mjs/components/Results/Results.js +3 -3
- package/lib/mjs/components/SelectTypeQuestion/SelectTypeQuestion.js +13 -20
- package/lib/mjs/components/ZeroResults/ZeroResults.js +2 -2
- package/lib/mjs/constants.js +4 -2
- package/lib/mjs/hooks/useCioClient.js +4 -3
- package/lib/mjs/hooks/useConsoleErrors.js +18 -0
- package/lib/mjs/hooks/useQuiz.js +47 -0
- package/lib/mjs/hooks/useQuizApiState.js +87 -0
- package/lib/mjs/hooks/useQuizEvents/index.js +33 -0
- package/lib/mjs/hooks/useQuizEvents/useQuizAddToCart.js +18 -0
- package/lib/mjs/hooks/useQuizEvents/useQuizBackClick.js +11 -0
- package/lib/mjs/hooks/useQuizEvents/useQuizNextClick.js +45 -0
- package/lib/mjs/hooks/useQuizEvents/useQuizResultClick.js +17 -0
- package/lib/mjs/hooks/useQuizEvents/useQuizResultsLoaded.js +19 -0
- package/lib/mjs/hooks/useQuizLocalState.js +17 -0
- package/lib/mjs/services/index.js +60 -0
- package/lib/mjs/utils.js +39 -17
- package/lib/styles.css +23 -20
- package/lib/types/components/CioQuiz/actions.d.ts +20 -0
- package/lib/types/components/CioQuiz/context.d.ts +10 -14
- package/lib/types/components/CioQuiz/index.d.ts +1 -12
- package/lib/types/components/CioQuiz/quizApiReducer.d.ts +14 -0
- package/lib/types/components/CioQuiz/{reducer.d.ts → quizLocalReducer.d.ts} +3 -3
- package/lib/types/components/QuizQuestions/index.d.ts +1 -4
- package/lib/types/components/ResultCard/ResultCard.d.ts +2 -4
- package/lib/types/components/ResultContainer/ResultContainer.d.ts +1 -2
- package/lib/types/components/ResultCtaButton/ResultCtaButton.d.ts +2 -3
- package/lib/types/components/ResultFilters/ResultFilters.d.ts +2 -5
- package/lib/types/components/Results/Results.d.ts +1 -7
- package/lib/types/components/ZeroResults/ZeroResults.d.ts +1 -1
- package/lib/types/constants.d.ts +2 -1
- package/lib/types/hooks/useCioClient.d.ts +1 -1
- package/lib/types/hooks/useConsoleErrors.d.ts +3 -0
- package/lib/types/hooks/useQuiz.d.ts +3 -0
- package/lib/types/hooks/useQuizApiState.d.ts +10 -0
- package/lib/types/hooks/useQuizEvents/index.d.ts +15 -0
- package/lib/types/hooks/useQuizEvents/useQuizAddToCart.d.ts +5 -0
- package/lib/types/hooks/useQuizEvents/useQuizBackClick.d.ts +4 -0
- package/lib/types/hooks/useQuizEvents/useQuizNextClick.d.ts +5 -0
- package/lib/types/hooks/useQuizEvents/useQuizResultClick.d.ts +5 -0
- package/lib/types/hooks/useQuizEvents/useQuizResultsLoaded.d.ts +5 -0
- package/lib/types/hooks/useQuizLocalState.d.ts +6 -0
- package/lib/types/services/index.d.ts +8 -0
- package/lib/types/types.d.ts +63 -0
- package/lib/types/utils.d.ts +12 -7
- package/package.json +12 -12
package/lib/cjs/utils.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.getFilterValuesFromExpression = exports.isFunction = exports.getPreferredColorScheme = exports.getQuestionTypes = exports.stringify = exports.stringifyWithDefaults = exports.defaultOnQuizResultsLoadedCode = exports.defaultOnQuizResultClickCode = exports.defaultOnAddToCartClickCode = exports.getStoryParams = exports.renderImages = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const react_1 = tslib_1.__importDefault(require("react"));
|
|
6
|
-
const constructorio_client_javascript_1 = tslib_1.__importDefault(require("@constructor-io/constructorio-client-javascript"));
|
|
7
6
|
const actions_1 = require("./components/CioQuiz/actions");
|
|
8
7
|
const renderImages = (images, cssClasses) => {
|
|
9
8
|
const { primary_url: primaryUrl, primary_alt: primaryAlt, secondary_url: secondaryUrl, secondary_alt: secondaryAlt, } = images;
|
|
@@ -42,25 +41,34 @@ ${templateCode}
|
|
|
42
41
|
};
|
|
43
42
|
};
|
|
44
43
|
exports.getStoryParams = getStoryParams;
|
|
45
|
-
exports.
|
|
44
|
+
exports.defaultOnAddToCartClickCode = `"onAddToCartClick": (item) => console.dir(item)`;
|
|
45
|
+
exports.defaultOnQuizResultClickCode = `"onQuizResultClick": (result, position) => console.dir(result, position)`;
|
|
46
|
+
exports.defaultOnQuizResultsLoadedCode = `"onQuizResultsLoaded": (results) => console.dir(results)`;
|
|
46
47
|
const stringifyWithDefaults = (obj) => {
|
|
47
|
-
const {
|
|
48
|
-
|
|
48
|
+
const { resultsPageOptions, cioJsClient } = obj, rest = tslib_1.__rest(obj, ["resultsPageOptions", "cioJsClient"]);
|
|
49
|
+
const { onAddToCartClick, onQuizResultsLoaded, onQuizResultClick } = resultsPageOptions;
|
|
50
|
+
let res = JSON.stringify(Object.assign(Object.assign({}, rest), { resultsPageOptions }), null, ' ');
|
|
49
51
|
if (cioJsClient) {
|
|
50
52
|
res = res.replace('"resultsPageOptions": {', `"cioJsClient": cioJsClient,
|
|
51
53
|
"resultsPageOptions": {`);
|
|
52
54
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
if (onQuizResultsLoaded) {
|
|
56
|
+
res = res.replace('"resultsPageOptions": {', `"resultsPageOptions": {
|
|
57
|
+
${exports.defaultOnQuizResultsLoadedCode},`);
|
|
58
|
+
}
|
|
59
|
+
if (onQuizResultClick) {
|
|
60
|
+
res = res.replace('"resultsPageOptions": {', `"resultsPageOptions": {
|
|
61
|
+
${exports.defaultOnQuizResultClickCode},`);
|
|
62
|
+
}
|
|
63
|
+
if (onAddToCartClick) {
|
|
64
|
+
res = res.replace('"resultsPageOptions": {', `"resultsPageOptions": {
|
|
65
|
+
${exports.defaultOnAddToCartClickCode},`);
|
|
66
|
+
}
|
|
55
67
|
return res;
|
|
56
68
|
};
|
|
57
69
|
exports.stringifyWithDefaults = stringifyWithDefaults;
|
|
58
70
|
const stringify = (obj) => JSON.stringify(obj, null, ' ');
|
|
59
71
|
exports.stringify = stringify;
|
|
60
|
-
const getNextQuestion = (cioClient, quizId, parameters) => cioClient === null || cioClient === void 0 ? void 0 : cioClient.quizzes.getQuizNextQuestion(quizId, parameters);
|
|
61
|
-
exports.getNextQuestion = getNextQuestion;
|
|
62
|
-
const getQuizResults = (cioClient, quizId, parameters) => tslib_1.__awaiter(void 0, void 0, void 0, function* () { return cioClient === null || cioClient === void 0 ? void 0 : cioClient.quizzes.getQuizResults(quizId, parameters); });
|
|
63
|
-
exports.getQuizResults = getQuizResults;
|
|
64
72
|
const getQuestionTypes = (questionType) => {
|
|
65
73
|
const isOpenQuestion = questionType === actions_1.QuestionTypes.OpenText;
|
|
66
74
|
const isCoverQuestion = questionType === actions_1.QuestionTypes.Cover;
|
|
@@ -76,16 +84,6 @@ const getQuestionTypes = (questionType) => {
|
|
|
76
84
|
};
|
|
77
85
|
};
|
|
78
86
|
exports.getQuestionTypes = getQuestionTypes;
|
|
79
|
-
const getCioClient = (apiKey) => {
|
|
80
|
-
if (apiKey) {
|
|
81
|
-
return new constructorio_client_javascript_1.default({
|
|
82
|
-
apiKey,
|
|
83
|
-
sendTrackingEvents: true,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
return undefined;
|
|
87
|
-
};
|
|
88
|
-
exports.getCioClient = getCioClient;
|
|
89
87
|
function getPreferredColorScheme() {
|
|
90
88
|
var _a;
|
|
91
89
|
let colorScheme = 'light';
|
|
@@ -96,3 +94,26 @@ function getPreferredColorScheme() {
|
|
|
96
94
|
return colorScheme;
|
|
97
95
|
}
|
|
98
96
|
exports.getPreferredColorScheme = getPreferredColorScheme;
|
|
97
|
+
function isFunction(fn) {
|
|
98
|
+
return fn && typeof fn === 'function';
|
|
99
|
+
}
|
|
100
|
+
exports.isFunction = isFunction;
|
|
101
|
+
const isValueExpression = (exp) => 'name' in exp && 'value' in exp;
|
|
102
|
+
const isAndFilter = (exp) => 'and' in exp;
|
|
103
|
+
const isOrFilter = (exp) => 'or' in exp;
|
|
104
|
+
const getFilterValuesFromExpression = (exp) => {
|
|
105
|
+
if (!exp) {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
if (isAndFilter(exp)) {
|
|
109
|
+
return exp.and.flatMap((innerExpression) => (0, exports.getFilterValuesFromExpression)(innerExpression));
|
|
110
|
+
}
|
|
111
|
+
if (isOrFilter(exp)) {
|
|
112
|
+
return exp.or.flatMap((innerExpression) => (0, exports.getFilterValuesFromExpression)(innerExpression));
|
|
113
|
+
}
|
|
114
|
+
if (isValueExpression(exp)) {
|
|
115
|
+
return [exp.value];
|
|
116
|
+
}
|
|
117
|
+
return [];
|
|
118
|
+
};
|
|
119
|
+
exports.getFilterValuesFromExpression = getFilterValuesFromExpression;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Local Actions
|
|
1
2
|
export var QuestionTypes;
|
|
2
3
|
(function (QuestionTypes) {
|
|
3
4
|
QuestionTypes["OpenText"] = "open";
|
|
@@ -7,3 +8,12 @@ export var QuestionTypes;
|
|
|
7
8
|
QuestionTypes["Back"] = "back";
|
|
8
9
|
QuestionTypes["Reset"] = "reset";
|
|
9
10
|
})(QuestionTypes || (QuestionTypes = {}));
|
|
11
|
+
// API actions
|
|
12
|
+
export var QuizAPIActionTypes;
|
|
13
|
+
(function (QuizAPIActionTypes) {
|
|
14
|
+
QuizAPIActionTypes[QuizAPIActionTypes["SET_IS_LOADING"] = 0] = "SET_IS_LOADING";
|
|
15
|
+
QuizAPIActionTypes[QuizAPIActionTypes["SET_IS_ERROR"] = 1] = "SET_IS_ERROR";
|
|
16
|
+
QuizAPIActionTypes[QuizAPIActionTypes["SET_QUIZ_RESULTS"] = 2] = "SET_QUIZ_RESULTS";
|
|
17
|
+
QuizAPIActionTypes[QuizAPIActionTypes["SET_CURRENT_QUESTION"] = 3] = "SET_CURRENT_QUESTION";
|
|
18
|
+
QuizAPIActionTypes[QuizAPIActionTypes["RESET_QUIZ"] = 4] = "RESET_QUIZ";
|
|
19
|
+
})(QuizAPIActionTypes || (QuizAPIActionTypes = {}));
|
|
@@ -1,120 +1,32 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import QuizContext from './context';
|
|
3
|
-
import reducer, { initialState } from './reducer';
|
|
4
|
-
import { QuestionTypes } from './actions';
|
|
5
3
|
import QuizQuestions from '../QuizQuestions';
|
|
6
4
|
import ResultContainer from '../ResultContainer/ResultContainer';
|
|
7
5
|
import { RequestStates } from '../../constants';
|
|
8
|
-
import { getNextQuestion, getQuizResults } from '../../utils';
|
|
9
6
|
import Spinner from '../Spinner/Spinner';
|
|
10
|
-
import
|
|
7
|
+
import useQuiz from '../../hooks/useQuiz';
|
|
11
8
|
export default function CioQuiz(props) {
|
|
12
|
-
const {
|
|
13
|
-
|
|
14
|
-
// eslint-disable-next-line no-console
|
|
15
|
-
console.error('quizId is a required field of type string');
|
|
16
|
-
}
|
|
17
|
-
if (!resultsPageOptions || Object.keys(resultsPageOptions).length === 0) {
|
|
18
|
-
// eslint-disable-next-line no-console
|
|
19
|
-
console.error('resultsPageOptions is a required field of type object');
|
|
20
|
-
}
|
|
21
|
-
if (resultsPageOptions && !resultsPageOptions?.addToCartCallback) {
|
|
22
|
-
// eslint-disable-next-line no-console
|
|
23
|
-
console.error('resultsPageOptions.addToCartCallback is a required field of type function');
|
|
24
|
-
}
|
|
25
|
-
const cioClient = useCioClient({ apiKey, cioJsClient });
|
|
26
|
-
const [state, dispatch] = useReducer(reducer, initialState);
|
|
27
|
-
const [requestState, setRequestState] = useState(RequestStates.Stale);
|
|
28
|
-
const [questionResponse, setQuestionResponse] = useState();
|
|
29
|
-
const [resultsResponse, setResultsResponse] = useState();
|
|
30
|
-
const [firstQuestion, setFirstQuestion] = useState();
|
|
31
|
-
const [quizVersionId, setQuizVersionId] = useState(quizVersionIdProp || '');
|
|
32
|
-
const [quizSessionId, setQuizSessionId] = useState('');
|
|
33
|
-
const isFirstQuestion = firstQuestion?.next_question.id === questionResponse?.next_question.id;
|
|
34
|
-
const quizNextHandler = useCallback((action) => {
|
|
35
|
-
if (action) {
|
|
36
|
-
dispatch(action);
|
|
37
|
-
}
|
|
38
|
-
}, [dispatch]);
|
|
39
|
-
const quizBackHandler = useCallback(() => {
|
|
40
|
-
if (dispatch) {
|
|
41
|
-
dispatch({ type: QuestionTypes.Back });
|
|
42
|
-
}
|
|
43
|
-
}, [dispatch]);
|
|
9
|
+
const { cioClient, state, events: { nextQuestion, previousQuestion, resetQuiz, addToCart, resultClick }, } = useQuiz(props);
|
|
10
|
+
const { resultsPageOptions } = props;
|
|
44
11
|
const contextValue = {
|
|
45
|
-
dispatch,
|
|
46
|
-
questionResponse,
|
|
47
|
-
state,
|
|
48
|
-
resultsResponse,
|
|
49
|
-
isFirstQuestion,
|
|
50
|
-
quizNextHandler,
|
|
51
|
-
quizBackHandler,
|
|
52
12
|
cioClient,
|
|
13
|
+
state,
|
|
14
|
+
nextQuestion,
|
|
15
|
+
previousQuestion,
|
|
16
|
+
resetQuiz,
|
|
17
|
+
addToCart,
|
|
18
|
+
resultClick,
|
|
19
|
+
customClickItemCallback: !!resultsPageOptions?.onQuizResultClick,
|
|
53
20
|
};
|
|
54
|
-
|
|
55
|
-
(async () => {
|
|
56
|
-
if (cioClient) {
|
|
57
|
-
setRequestState(RequestStates.Loading);
|
|
58
|
-
if (state.isLastAnswer) {
|
|
59
|
-
try {
|
|
60
|
-
const quizResults = await getQuizResults(cioClient, quizId, {
|
|
61
|
-
answers: state.answers,
|
|
62
|
-
resultsPerPage: resultsPageOptions?.numResultsToDisplay,
|
|
63
|
-
quizVersionId,
|
|
64
|
-
quizSessionId,
|
|
65
|
-
});
|
|
66
|
-
setResultsResponse(quizResults);
|
|
67
|
-
setRequestState(RequestStates.Success);
|
|
68
|
-
setQuestionResponse(undefined);
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
setResultsResponse(undefined);
|
|
72
|
-
setRequestState(RequestStates.Error);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
try {
|
|
77
|
-
const questionResult = await getNextQuestion(cioClient, quizId, {
|
|
78
|
-
answers: state.answers,
|
|
79
|
-
quizVersionId,
|
|
80
|
-
quizSessionId,
|
|
81
|
-
});
|
|
82
|
-
setQuestionResponse(questionResult);
|
|
83
|
-
setRequestState(RequestStates.Success);
|
|
84
|
-
setResultsResponse(undefined);
|
|
85
|
-
if (!quizVersionId && questionResult?.quiz_version_id) {
|
|
86
|
-
setQuizVersionId(questionResult.quiz_version_id);
|
|
87
|
-
}
|
|
88
|
-
if (!quizSessionId && questionResult?.quiz_session_id) {
|
|
89
|
-
setQuizSessionId(questionResult.quiz_session_id);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
setRequestState(RequestStates.Error);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
})();
|
|
98
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
99
|
-
}, [cioClient, state, quizId, state.isLastAnswer, resultsPageOptions?.numResultsToDisplay]);
|
|
100
|
-
useEffect(() => {
|
|
101
|
-
if (!firstQuestion) {
|
|
102
|
-
setFirstQuestion(questionResponse);
|
|
103
|
-
}
|
|
104
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
105
|
-
}, [questionResponse]);
|
|
106
|
-
const resetQuizSessionId = () => {
|
|
107
|
-
setQuizSessionId('');
|
|
108
|
-
};
|
|
109
|
-
if (requestState === RequestStates.Loading) {
|
|
21
|
+
if (state.quiz.requestState === RequestStates.Loading) {
|
|
110
22
|
return (React.createElement("div", { className: 'cio-quiz' },
|
|
111
23
|
React.createElement(Spinner, null)));
|
|
112
24
|
}
|
|
113
|
-
if (requestState === RequestStates.Success) {
|
|
25
|
+
if (state.quiz.requestState === RequestStates.Success) {
|
|
114
26
|
return (React.createElement("div", { className: 'cio-quiz' },
|
|
115
27
|
React.createElement(QuizContext.Provider, { value: contextValue },
|
|
116
|
-
|
|
117
|
-
|
|
28
|
+
state.quiz.results && React.createElement(ResultContainer, { options: resultsPageOptions }),
|
|
29
|
+
state.quiz.currentQuestion && React.createElement(QuizQuestions, null))));
|
|
118
30
|
}
|
|
119
31
|
return null;
|
|
120
32
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { RequestStates } from '../../constants';
|
|
2
|
+
import { getFilterValuesFromExpression } from '../../utils';
|
|
3
|
+
import { QuizAPIActionTypes } from './actions';
|
|
4
|
+
export const initialState = {
|
|
5
|
+
quizRequestState: RequestStates.Stale,
|
|
6
|
+
};
|
|
7
|
+
export default function apiReducer(state, action) {
|
|
8
|
+
switch (action.type) {
|
|
9
|
+
case QuizAPIActionTypes.SET_IS_LOADING:
|
|
10
|
+
return {
|
|
11
|
+
...state,
|
|
12
|
+
quizRequestState: RequestStates.Loading,
|
|
13
|
+
};
|
|
14
|
+
case QuizAPIActionTypes.SET_IS_ERROR:
|
|
15
|
+
return {
|
|
16
|
+
...state,
|
|
17
|
+
quizRequestState: RequestStates.Error,
|
|
18
|
+
quizResults: undefined,
|
|
19
|
+
};
|
|
20
|
+
case QuizAPIActionTypes.SET_CURRENT_QUESTION:
|
|
21
|
+
return {
|
|
22
|
+
...state,
|
|
23
|
+
quizRequestState: RequestStates.Success,
|
|
24
|
+
quizVersionId: action.payload?.quizVersionId,
|
|
25
|
+
quizSessionId: action.payload?.quizSessionId,
|
|
26
|
+
quizCurrentQuestion: action.payload?.quizCurrentQuestion,
|
|
27
|
+
quizResults: undefined,
|
|
28
|
+
// If no current question set first question value
|
|
29
|
+
...(!state.quizCurrentQuestion && {
|
|
30
|
+
quizFirstQuestion: action.payload?.quizCurrentQuestion,
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
case QuizAPIActionTypes.SET_QUIZ_RESULTS: {
|
|
34
|
+
const filterExpression = action.payload?.quizResults?.request?.collection_filter_expression || null;
|
|
35
|
+
const quizResultsFilters = [...new Set(getFilterValuesFromExpression(filterExpression))];
|
|
36
|
+
return {
|
|
37
|
+
...state,
|
|
38
|
+
quizRequestState: RequestStates.Success,
|
|
39
|
+
quizResults: action.payload?.quizResults,
|
|
40
|
+
quizResultsFilters,
|
|
41
|
+
quizCurrentQuestion: undefined,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
case QuizAPIActionTypes.RESET_QUIZ:
|
|
45
|
+
return initialState;
|
|
46
|
+
default:
|
|
47
|
+
return state;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -10,7 +10,7 @@ function answerInputReducer(state, action) {
|
|
|
10
10
|
[String(action.payload.questionId)]: action.payload.input,
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
|
-
export default function
|
|
13
|
+
export default function quizLocalReducer(state, action) {
|
|
14
14
|
switch (action.type) {
|
|
15
15
|
case QuestionTypes.OpenText:
|
|
16
16
|
return {
|
|
@@ -3,23 +3,17 @@ import QuestionTitle from '../QuestionTitle/QuestionTitle';
|
|
|
3
3
|
import QuizContext from '../CioQuiz/context';
|
|
4
4
|
import QuestionDescription from '../QuestionDescription/QuestionDescription';
|
|
5
5
|
import { renderImages } from '../../utils';
|
|
6
|
-
import { QuestionTypes } from '../CioQuiz/actions';
|
|
7
6
|
import ControlBar from '../ControlBar/ControlBar';
|
|
8
7
|
export default function CoverTypeQuestion() {
|
|
9
|
-
const {
|
|
8
|
+
const { state, previousQuestion, nextQuestion } = useContext(QuizContext);
|
|
10
9
|
let question;
|
|
11
|
-
if (
|
|
12
|
-
question =
|
|
10
|
+
if (state?.quiz.currentQuestion) {
|
|
11
|
+
question = state?.quiz.currentQuestion.next_question;
|
|
13
12
|
}
|
|
14
13
|
const hasImage = question?.images?.primary_url;
|
|
15
14
|
const onNextClick = () => {
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
type: QuestionTypes.Cover,
|
|
19
|
-
payload: {
|
|
20
|
-
isLastQuestion: questionResponse?.is_last_question,
|
|
21
|
-
},
|
|
22
|
-
});
|
|
15
|
+
if (nextQuestion) {
|
|
16
|
+
nextQuestion();
|
|
23
17
|
}
|
|
24
18
|
};
|
|
25
19
|
if (question) {
|
|
@@ -30,7 +24,7 @@ export default function CoverTypeQuestion() {
|
|
|
30
24
|
React.createElement("div", { className: 'cio-question-content' },
|
|
31
25
|
React.createElement(QuestionTitle, { title: question?.title }),
|
|
32
26
|
React.createElement(QuestionDescription, { description: question.description }),
|
|
33
|
-
React.createElement(ControlBar, { nextButtonHandler: onNextClick, backButtonHandler:
|
|
27
|
+
React.createElement(ControlBar, { nextButtonHandler: onNextClick, backButtonHandler: previousQuestion, showBackButton: !state?.quiz.isFirstQuestion, ctaButtonText: question?.cta_text })),
|
|
34
28
|
hasImage ? renderImages(question.images, 'cio-question-image-container') : ''));
|
|
35
29
|
}
|
|
36
30
|
return null;
|
|
@@ -3,15 +3,14 @@ import QuestionTitle from '../QuestionTitle/QuestionTitle';
|
|
|
3
3
|
import QuestionDescription from '../QuestionDescription/QuestionDescription';
|
|
4
4
|
import { renderImages } from '../../utils';
|
|
5
5
|
import QuizContext from '../CioQuiz/context';
|
|
6
|
-
import { QuestionTypes } from '../CioQuiz/actions';
|
|
7
6
|
import ControlBar from '../ControlBar/ControlBar';
|
|
8
7
|
function OpenTextQuestion(props) {
|
|
9
8
|
const { initialValue = '', onChangeHandler: userDefinedHandler = null } = props;
|
|
10
|
-
const {
|
|
9
|
+
const { state, previousQuestion, nextQuestion } = useContext(QuizContext);
|
|
11
10
|
const [openTextInput, setOpenTextInput] = useState(initialValue);
|
|
12
11
|
let question;
|
|
13
|
-
if (
|
|
14
|
-
question =
|
|
12
|
+
if (state?.quiz.currentQuestion) {
|
|
13
|
+
question = state?.quiz.currentQuestion.next_question;
|
|
15
14
|
}
|
|
16
15
|
const onChangeHandler = (e) => {
|
|
17
16
|
setOpenTextInput(e.target.value);
|
|
@@ -20,15 +19,8 @@ function OpenTextQuestion(props) {
|
|
|
20
19
|
}
|
|
21
20
|
};
|
|
22
21
|
const onNextClick = () => {
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
type: QuestionTypes.OpenText,
|
|
26
|
-
payload: {
|
|
27
|
-
questionId: questionResponse.next_question.id,
|
|
28
|
-
input: openTextInput,
|
|
29
|
-
isLastQuestion: questionResponse.is_last_question,
|
|
30
|
-
},
|
|
31
|
-
});
|
|
22
|
+
if (nextQuestion && openTextInput) {
|
|
23
|
+
nextQuestion(openTextInput);
|
|
32
24
|
}
|
|
33
25
|
};
|
|
34
26
|
const onKeyDownHandler = (e) => {
|
|
@@ -38,11 +30,13 @@ function OpenTextQuestion(props) {
|
|
|
38
30
|
}
|
|
39
31
|
};
|
|
40
32
|
useEffect(() => {
|
|
41
|
-
if (
|
|
42
|
-
const
|
|
33
|
+
if (state?.quiz.currentQuestion) {
|
|
34
|
+
const questionId = state?.quiz.currentQuestion?.next_question.id;
|
|
35
|
+
const currentAnswer = state.answers.inputs?.[questionId];
|
|
36
|
+
const openTextAnswer = currentAnswer || initialValue;
|
|
43
37
|
setOpenTextInput(openTextAnswer);
|
|
44
38
|
}
|
|
45
|
-
}, [
|
|
39
|
+
}, [state, initialValue]);
|
|
46
40
|
if (question) {
|
|
47
41
|
const hasImage = question?.images?.primary_url;
|
|
48
42
|
return (React.createElement("div", { className: `
|
|
@@ -54,7 +48,7 @@ function OpenTextQuestion(props) {
|
|
|
54
48
|
React.createElement(QuestionTitle, { title: question.title }),
|
|
55
49
|
React.createElement(QuestionDescription, { description: question.description }),
|
|
56
50
|
React.createElement("input", { className: 'cio-question-input-text', placeholder: question.input_placeholder || 'Answer here...', value: openTextInput, onChange: onChangeHandler, onKeyDown: onKeyDownHandler }),
|
|
57
|
-
React.createElement(ControlBar, { nextButtonHandler: onNextClick, isNextButtonDisabled: !openTextInput, backButtonHandler:
|
|
51
|
+
React.createElement(ControlBar, { nextButtonHandler: onNextClick, isNextButtonDisabled: !openTextInput, backButtonHandler: previousQuestion, showBackButton: !state?.quiz.isFirstQuestion, ctaButtonText: question?.cta_text }))));
|
|
58
52
|
}
|
|
59
53
|
return null;
|
|
60
54
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
2
|
import OpenTextQuestion from '../OpenTextTypeQuestion/OpenTextTypeQuestion';
|
|
3
3
|
import CoverTypeQuestion from '../CoverTypeQuestion/CoverTypeQuestion';
|
|
4
4
|
import SelectTypeQuestion from '../SelectTypeQuestion/SelectTypeQuestion';
|
|
5
5
|
import { getQuestionTypes } from '../../utils';
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import QuizContext from '../CioQuiz/context';
|
|
7
|
+
export default function QuizQuestions() {
|
|
8
|
+
const { state } = useContext(QuizContext);
|
|
9
|
+
const nextQuestion = state?.quiz.currentQuestion?.next_question;
|
|
8
10
|
const questionTypes = getQuestionTypes(nextQuestion?.type);
|
|
9
11
|
return (React.createElement(React.Fragment, null,
|
|
10
12
|
questionTypes.isOpenQuestion && React.createElement(OpenTextQuestion, { key: nextQuestion?.id }),
|
|
@@ -2,39 +2,20 @@ import React, { useContext } from 'react';
|
|
|
2
2
|
import ResultCtaButton from '../ResultCtaButton/ResultCtaButton';
|
|
3
3
|
import QuizContext from '../CioQuiz/context';
|
|
4
4
|
export default function ResultCard(props) {
|
|
5
|
-
const { result,
|
|
6
|
-
const {
|
|
5
|
+
const { result, salePriceKey, regularPriceKey, resultPosition } = props;
|
|
6
|
+
const { resultClick, customClickItemCallback } = useContext(QuizContext);
|
|
7
7
|
const salePrice = salePriceKey && result?.data?.[salePriceKey];
|
|
8
8
|
const regularPrice = regularPriceKey && result?.data?.[regularPriceKey];
|
|
9
|
-
const clickItemCallback = () => {
|
|
10
|
-
/* eslint-disable @typescript-eslint/naming-convention */
|
|
11
|
-
if (resultsResponse && resultsResponse.request && resultsResponse.response) {
|
|
12
|
-
const { quiz_id, quiz_session_id, quiz_version_id, result_id, request: { section, page, num_results_per_page }, response: { total_num_results }, } = resultsResponse;
|
|
13
|
-
/* eslint-enable @typescript-eslint/naming-convention */
|
|
14
|
-
cioClient?.tracker.trackQuizResultClick({
|
|
15
|
-
quiz_id,
|
|
16
|
-
quiz_version_id,
|
|
17
|
-
quiz_session_id,
|
|
18
|
-
item_id: result.data?.id,
|
|
19
|
-
item_name: result?.value,
|
|
20
|
-
section,
|
|
21
|
-
result_count: total_num_results,
|
|
22
|
-
result_page: page,
|
|
23
|
-
result_id,
|
|
24
|
-
result_position_on_page: resultPosition,
|
|
25
|
-
num_results_per_page,
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
if (customClickItemCallback && typeof customClickItemCallback === 'function') {
|
|
29
|
-
customClickItemCallback(result);
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
9
|
const clickHandler = () => {
|
|
33
|
-
|
|
10
|
+
if (resultClick) {
|
|
11
|
+
resultClick(result, resultPosition);
|
|
12
|
+
}
|
|
34
13
|
};
|
|
35
14
|
const keyDownHandler = (event) => {
|
|
36
15
|
if (event?.key === ' ' || event?.key === 'Enter') {
|
|
37
|
-
|
|
16
|
+
if (resultClick) {
|
|
17
|
+
resultClick(result, resultPosition);
|
|
18
|
+
}
|
|
38
19
|
}
|
|
39
20
|
};
|
|
40
21
|
const resultCardContent = () => (React.createElement(React.Fragment, null,
|
|
@@ -49,7 +30,7 @@ export default function ResultCard(props) {
|
|
|
49
30
|
regularPrice && (React.createElement("span", { className: `cio-result-card-regular-price${salePrice ? '--strike-through' : ''}` },
|
|
50
31
|
"$",
|
|
51
32
|
regularPrice)))),
|
|
52
|
-
React.createElement(ResultCtaButton, { item: result,
|
|
33
|
+
React.createElement(ResultCtaButton, { item: result, price: salePrice || regularPrice })));
|
|
53
34
|
const resultCardContentWithLink = () => (React.createElement("a", { className: 'cio-result-card-anchor', href: result.data?.url }, resultCardContent()));
|
|
54
|
-
return (React.createElement("div", { onClick: clickHandler, onKeyDown: keyDownHandler, className: 'cio-result-card', role: 'button', tabIndex: 0 }, !customClickItemCallback ? resultCardContentWithLink() : resultCardContent()));
|
|
35
|
+
return (React.createElement("div", { onClick: () => clickHandler(), onKeyDown: (e) => keyDownHandler(e), className: 'cio-result-card', role: 'button', tabIndex: 0 }, !customClickItemCallback ? resultCardContentWithLink() : resultCardContent()));
|
|
55
36
|
}
|
|
@@ -1,45 +1,22 @@
|
|
|
1
1
|
import React, { useContext } from 'react';
|
|
2
2
|
import RedoButton from '../RedoButton/RedoButton';
|
|
3
|
-
import { QuestionTypes } from '../CioQuiz/actions';
|
|
4
3
|
import QuizContext from '../CioQuiz/context';
|
|
5
4
|
import ResultFilters from '../ResultFilters/ResultFilters';
|
|
6
5
|
import ZeroResults from '../ZeroResults/ZeroResults';
|
|
7
6
|
import Results from '../Results/Results';
|
|
8
7
|
export default function ResultContainer(props) {
|
|
9
|
-
const { options
|
|
10
|
-
const {
|
|
11
|
-
const {
|
|
12
|
-
const
|
|
13
|
-
const zeroResults = !resultsResponse?.response?.results?.length;
|
|
8
|
+
const { options } = props;
|
|
9
|
+
const { resultCardSalePriceKey, resultCardRegularPriceKey } = options;
|
|
10
|
+
const { state, resetQuiz } = useContext(QuizContext);
|
|
11
|
+
const zeroResults = !state?.quiz.results?.response?.results?.length;
|
|
14
12
|
const resultsTitle = zeroResults ? 'Oops, there are no results' : 'Here are your results';
|
|
15
|
-
|
|
16
|
-
if (dispatch && resultsResponse) {
|
|
17
|
-
resetQuizSessionId();
|
|
18
|
-
dispatch({
|
|
19
|
-
type: QuestionTypes.Reset,
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
if (resultsResponse && resultsResponse.request && resultsResponse.response) {
|
|
24
|
-
/* eslint-disable @typescript-eslint/naming-convention */
|
|
25
|
-
const { quiz_id, quiz_session_id, quiz_version_id, result_id, request: { section, page }, response: { total_num_results }, } = resultsResponse;
|
|
26
|
-
/* eslint-enable @typescript-eslint/naming-convention */
|
|
27
|
-
cioClient?.tracker.trackQuizResultsLoaded({
|
|
28
|
-
quiz_id,
|
|
29
|
-
quiz_version_id,
|
|
30
|
-
quiz_session_id,
|
|
31
|
-
url: window.location.href,
|
|
32
|
-
section,
|
|
33
|
-
result_count: total_num_results,
|
|
34
|
-
result_page: page,
|
|
35
|
-
result_id,
|
|
36
|
-
});
|
|
13
|
+
if (state?.quiz.results) {
|
|
37
14
|
return (React.createElement("div", { className: 'cio-results-container' },
|
|
38
15
|
React.createElement("h1", { className: 'cio-results-title' }, resultsTitle),
|
|
39
16
|
React.createElement("div", { className: 'cio-results-filter-and-redo-container' },
|
|
40
|
-
React.createElement(ResultFilters,
|
|
41
|
-
React.createElement(RedoButton, { onClick:
|
|
42
|
-
!zeroResults && (React.createElement(Results, {
|
|
17
|
+
React.createElement(ResultFilters, null),
|
|
18
|
+
React.createElement(RedoButton, { onClick: resetQuiz })),
|
|
19
|
+
!zeroResults && (React.createElement(Results, { resultCardSalePriceKey: resultCardSalePriceKey, resultCardRegularPriceKey: resultCardRegularPriceKey })),
|
|
43
20
|
zeroResults && React.createElement(ZeroResults, null)));
|
|
44
21
|
}
|
|
45
22
|
return React.createElement("div", null, "Loading");
|
|
@@ -1,29 +1,11 @@
|
|
|
1
1
|
import React, { useContext } from 'react';
|
|
2
2
|
import QuizContext from '../CioQuiz/context';
|
|
3
3
|
export default function ResultCtaButton(props) {
|
|
4
|
-
const { item,
|
|
5
|
-
const {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
/* eslint-enable @typescript-eslint/naming-convention */
|
|
12
|
-
cioClient?.tracker.trackQuizConversion({
|
|
13
|
-
quiz_id,
|
|
14
|
-
quiz_version_id,
|
|
15
|
-
quiz_session_id,
|
|
16
|
-
item_id: item.data?.id,
|
|
17
|
-
item_name: item.value,
|
|
18
|
-
section,
|
|
19
|
-
variation_id: item.data?.variation_id,
|
|
20
|
-
revenue: (price && String(price)) || undefined,
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
if (callback && typeof callback === 'function') {
|
|
24
|
-
e.stopPropagation();
|
|
25
|
-
callback(item);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
return (React.createElement("button", { type: 'button', className: `cio-result-card-cta-button ${className || ''}`, onClick: clickHandler }, "Add to Cart"));
|
|
4
|
+
const { item, className, price } = props;
|
|
5
|
+
const { addToCart } = useContext(QuizContext);
|
|
6
|
+
return (React.createElement("button", { type: 'button', className: `cio-result-card-cta-button ${className || ''}`, onClick: (e) => {
|
|
7
|
+
if (addToCart) {
|
|
8
|
+
addToCart(e, item, price);
|
|
9
|
+
}
|
|
10
|
+
} }, "Add to Cart"));
|
|
29
11
|
}
|
|
@@ -1,27 +1,9 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const isAndFilter = (exp) => 'and' in exp;
|
|
6
|
-
const isOrFilter = (exp) => 'or' in exp;
|
|
7
|
-
const getFilterValuesFromExpression = (exp) => {
|
|
8
|
-
if (!exp) {
|
|
9
|
-
return [];
|
|
10
|
-
}
|
|
11
|
-
if (isAndFilter(exp)) {
|
|
12
|
-
return exp.and.flatMap((innerExpression) => getFilterValuesFromExpression(innerExpression));
|
|
13
|
-
}
|
|
14
|
-
if (isOrFilter(exp)) {
|
|
15
|
-
return exp.or.flatMap((innerExpression) => getFilterValuesFromExpression(innerExpression));
|
|
16
|
-
}
|
|
17
|
-
if (isValueExpression(exp)) {
|
|
18
|
-
return [exp.value];
|
|
19
|
-
}
|
|
20
|
-
return [];
|
|
21
|
-
};
|
|
22
|
-
const filterValues = [...new Set(getFilterValuesFromExpression(filters))];
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
|
+
import QuizContext from '../CioQuiz/context';
|
|
3
|
+
function ResultFilters() {
|
|
4
|
+
const { state } = useContext(QuizContext);
|
|
23
5
|
return (React.createElement("div", { className: 'cio-results-filter-container' },
|
|
24
6
|
React.createElement("p", null, "Because you answered"),
|
|
25
|
-
React.createElement("div", { className: 'cio-results-filter-options' },
|
|
7
|
+
React.createElement("div", { className: 'cio-results-filter-options' }, state?.quiz.resultsFilters?.map((filter) => (React.createElement("div", { className: 'cio-results-filter-option', key: filter }, filter))))));
|
|
26
8
|
}
|
|
27
9
|
export default ResultFilters;
|
|
@@ -2,8 +2,8 @@ import React, { useContext } from 'react';
|
|
|
2
2
|
import QuizContext from '../CioQuiz/context';
|
|
3
3
|
import ResultCard from '../ResultCard/ResultCard';
|
|
4
4
|
function Results(props) {
|
|
5
|
-
const {
|
|
6
|
-
const {
|
|
7
|
-
return (React.createElement("div", { className: 'cio-results' },
|
|
5
|
+
const { resultCardSalePriceKey, resultCardRegularPriceKey } = props;
|
|
6
|
+
const { state } = useContext(QuizContext);
|
|
7
|
+
return (React.createElement("div", { className: 'cio-results' }, state?.quiz?.results?.response?.results?.map((result, index) => (React.createElement(ResultCard, { result: result, key: result.data?.id, salePriceKey: resultCardSalePriceKey, regularPriceKey: resultCardRegularPriceKey, resultPosition: index + 1 })))));
|
|
8
8
|
}
|
|
9
9
|
export default Results;
|