@constructor-io/constructorio-ui-quizzes 1.3.11 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constructorio-ui-quizzes-bundled.js +20 -20
- package/lib/cjs/components/BackButton/BackButton.js +12 -7
- package/lib/cjs/components/CTAButton/CTAButton.js +4 -3
- package/lib/cjs/components/CioQuiz/actions.js +1 -0
- package/lib/cjs/components/CioQuiz/index.js +16 -9
- package/lib/cjs/components/CioQuiz/quizApiReducer.js +15 -9
- package/lib/cjs/components/CioQuiz/quizLocalReducer.js +36 -8
- package/lib/cjs/components/ControlBar/ControlBar.js +6 -4
- package/lib/cjs/components/CoverTypeQuestion/CoverTypeQuestion.js +2 -7
- package/lib/cjs/components/OpenTextTypeQuestion/OpenTextTypeQuestion.js +4 -32
- package/lib/cjs/components/RedoButton/RedoButton.js +11 -4
- package/lib/cjs/components/ResultCard/ResultCard.js +3 -18
- package/lib/cjs/components/ResultContainer/ResultContainer.js +2 -2
- package/lib/cjs/components/ResultCtaButton/ResultCtaButton.js +8 -7
- package/lib/cjs/components/ResultHeroCard/ResultHeroCard.js +36 -0
- package/lib/cjs/components/SelectTypeQuestion/SelectTypeQuestion.js +6 -56
- package/lib/cjs/components/SessionPromptModal/SessionPromptModal.js +18 -6
- package/lib/cjs/components/ZeroResults/ZeroResults.js +5 -4
- package/lib/cjs/constants.js +40 -5
- package/lib/cjs/hooks/useConsoleErrors.js +2 -1
- package/lib/cjs/hooks/usePropsGetters/index.js +85 -0
- package/lib/cjs/hooks/usePropsGetters/useCoverQuestionProps.js +13 -0
- package/lib/cjs/hooks/usePropsGetters/useNextQuestionButtonProps.js +24 -0
- package/lib/cjs/hooks/usePropsGetters/useOpenTextInputProps.js +47 -0
- package/lib/cjs/hooks/usePropsGetters/usePreviousQuestionButtonProps.js +18 -0
- package/lib/cjs/hooks/usePropsGetters/useSelectInputProps.js +84 -0
- package/lib/cjs/hooks/useQuiz.js +14 -28
- package/lib/cjs/hooks/useQuizEvents/index.js +21 -16
- package/lib/cjs/hooks/useQuizEvents/useHydrateQuizLocalState.js +18 -0
- package/lib/cjs/hooks/useQuizEvents/useQuizAddToCart.js +2 -2
- package/lib/cjs/hooks/useQuizEvents/useQuizAnswerChangeHandler.js +48 -0
- package/lib/cjs/hooks/useQuizEvents/useQuizBackClick.js +5 -5
- package/lib/cjs/hooks/useQuizEvents/useQuizNextClick.js +13 -39
- package/lib/cjs/hooks/useQuizEvents/useQuizResetClick.js +20 -0
- package/lib/cjs/hooks/useQuizEvents/useQuizResultClick.js +2 -2
- package/lib/cjs/hooks/useQuizState/index.js +21 -0
- package/lib/cjs/hooks/{useQuizApiState.js → useQuizState/useQuizApiState.js} +15 -29
- package/lib/cjs/hooks/useQuizState/useQuizLocalState.js +30 -0
- package/lib/cjs/index.js +25 -0
- package/lib/cjs/services/index.js +3 -3
- package/lib/cjs/stories/Quiz/tests/mocks.js +69 -14
- package/lib/cjs/utils.js +23 -1
- package/lib/mjs/components/BackButton/BackButton.js +12 -7
- package/lib/mjs/components/CTAButton/CTAButton.js +4 -3
- package/lib/mjs/components/CioQuiz/actions.js +1 -0
- package/lib/mjs/components/CioQuiz/index.js +16 -9
- package/lib/mjs/components/CioQuiz/quizApiReducer.js +19 -7
- package/lib/mjs/components/CioQuiz/quizLocalReducer.js +36 -7
- package/lib/mjs/components/ControlBar/ControlBar.js +6 -4
- package/lib/mjs/components/CoverTypeQuestion/CoverTypeQuestion.js +2 -7
- package/lib/mjs/components/OpenTextTypeQuestion/OpenTextTypeQuestion.js +5 -32
- package/lib/mjs/components/RedoButton/RedoButton.js +11 -4
- package/lib/mjs/components/ResultCard/ResultCard.js +3 -15
- package/lib/mjs/components/ResultContainer/ResultContainer.js +2 -2
- package/lib/mjs/components/ResultCtaButton/ResultCtaButton.js +8 -7
- package/lib/mjs/components/ResultHeroCard/ResultHeroCard.js +31 -0
- package/lib/mjs/components/SelectTypeQuestion/SelectTypeQuestion.js +6 -55
- package/lib/mjs/components/SessionPromptModal/SessionPromptModal.js +18 -6
- package/lib/mjs/components/ZeroResults/ZeroResults.js +5 -4
- package/lib/mjs/constants.js +39 -4
- package/lib/mjs/hooks/useConsoleErrors.js +2 -1
- package/lib/mjs/hooks/usePropsGetters/index.js +72 -0
- package/lib/mjs/hooks/usePropsGetters/useCoverQuestionProps.js +10 -0
- package/lib/mjs/hooks/usePropsGetters/useNextQuestionButtonProps.js +20 -0
- package/lib/mjs/hooks/usePropsGetters/useOpenTextInputProps.js +43 -0
- package/lib/mjs/hooks/usePropsGetters/usePreviousQuestionButtonProps.js +14 -0
- package/lib/mjs/hooks/usePropsGetters/useSelectInputProps.js +79 -0
- package/lib/mjs/hooks/useQuiz.js +13 -21
- package/lib/mjs/hooks/useQuizEvents/index.js +21 -16
- package/lib/mjs/hooks/useQuizEvents/useHydrateQuizLocalState.js +16 -0
- package/lib/mjs/hooks/useQuizEvents/useQuizAddToCart.js +2 -2
- package/lib/mjs/hooks/useQuizEvents/useQuizAnswerChangeHandler.js +45 -0
- package/lib/mjs/hooks/useQuizEvents/useQuizBackClick.js +5 -5
- package/lib/mjs/hooks/useQuizEvents/useQuizNextClick.js +12 -38
- package/lib/mjs/hooks/useQuizEvents/useQuizResetClick.js +18 -0
- package/lib/mjs/hooks/useQuizEvents/useQuizResultClick.js +2 -2
- package/lib/mjs/hooks/useQuizState/index.js +18 -0
- package/lib/mjs/hooks/{useQuizApiState.js → useQuizState/useQuizApiState.js} +15 -28
- package/lib/mjs/hooks/useQuizState/useQuizLocalState.js +26 -0
- package/lib/mjs/index.js +14 -0
- package/lib/mjs/services/index.js +1 -1
- package/lib/mjs/stories/Quiz/tests/mocks.js +75 -12
- package/lib/mjs/utils.js +17 -0
- package/lib/styles.css +13 -2
- package/lib/types/components/BackButton/BackButton.d.ts +2 -2
- package/lib/types/components/CTAButton/CTAButton.d.ts +1 -0
- package/lib/types/components/CioQuiz/actions.d.ts +5 -6
- package/lib/types/components/CioQuiz/context.d.ts +13 -6
- package/lib/types/components/CioQuiz/quizApiReducer.d.ts +2 -2
- package/lib/types/components/CioQuiz/quizLocalReducer.d.ts +3 -5
- package/lib/types/components/ControlBar/ControlBar.d.ts +0 -4
- package/lib/types/components/OpenTextTypeQuestion/OpenTextTypeQuestion.d.ts +2 -6
- package/lib/types/components/RedoButton/RedoButton.d.ts +1 -1
- package/lib/types/components/ResultCtaButton/ResultCtaButton.d.ts +1 -2
- package/lib/types/components/ResultHeroCard/ResultHeroCard.d.ts +7 -0
- package/lib/types/components/SelectTypeQuestion/SelectTypeQuestion.d.ts +3 -0
- package/lib/types/components/ZeroResults/ZeroResults.d.ts +2 -5
- package/lib/types/constants.d.ts +5 -4
- package/lib/types/hooks/useConsoleErrors.d.ts +2 -2
- package/lib/types/hooks/usePropsGetters/index.d.ts +18 -0
- package/lib/types/hooks/usePropsGetters/useCoverQuestionProps.d.ts +2 -0
- package/lib/types/hooks/usePropsGetters/useNextQuestionButtonProps.d.ts +4 -0
- package/lib/types/hooks/usePropsGetters/useOpenTextInputProps.d.ts +2 -0
- package/lib/types/hooks/usePropsGetters/usePreviousQuestionButtonProps.d.ts +3 -0
- package/lib/types/hooks/usePropsGetters/useSelectInputProps.d.ts +2 -0
- package/lib/types/hooks/useQuizEvents/index.d.ts +4 -16
- package/lib/types/hooks/useQuizEvents/useHydrateQuizLocalState.d.ts +4 -0
- package/lib/types/hooks/useQuizEvents/useQuizAnswerChangeHandler.d.ts +5 -0
- package/lib/types/hooks/useQuizEvents/useQuizBackClick.d.ts +2 -1
- package/lib/types/hooks/useQuizEvents/useQuizNextClick.d.ts +2 -1
- package/lib/types/hooks/useQuizEvents/useQuizResetClick.d.ts +4 -0
- package/lib/types/hooks/useQuizState/index.d.ts +16 -0
- package/lib/types/hooks/useQuizState/useQuizApiState.d.ts +12 -0
- package/lib/types/hooks/useQuizState/useQuizLocalState.d.ts +8 -0
- package/lib/types/index.d.ts +11 -0
- package/lib/types/services/index.d.ts +1 -1
- package/lib/types/stories/Quiz/tests/mocks.d.ts +5 -2
- package/lib/types/types.d.ts +121 -11
- package/lib/types/utils.d.ts +4 -0
- package/package.json +1 -1
- package/lib/cjs/hooks/useQuizLocalState.js +0 -54
- package/lib/mjs/hooks/useQuizLocalState.js +0 -48
- package/lib/types/hooks/useQuizApiState.d.ts +0 -11
- package/lib/types/hooks/useQuizLocalState.d.ts +0 -10
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
2
|
import BackButton from '../BackButton/BackButton';
|
|
3
3
|
import CTAButton from '../CTAButton/CTAButton';
|
|
4
|
+
import QuizContext from '../CioQuiz/context';
|
|
4
5
|
function ControlBar(props) {
|
|
5
|
-
const {
|
|
6
|
+
const { ctaButtonText } = props;
|
|
7
|
+
const { getNextQuestionButtonProps } = useContext(QuizContext);
|
|
6
8
|
return (React.createElement("div", { className: 'cio-question-buttons-container' },
|
|
7
|
-
|
|
8
|
-
React.createElement(CTAButton, {
|
|
9
|
+
React.createElement(BackButton, null),
|
|
10
|
+
React.createElement(CTAButton, { ctaText: ctaButtonText, propsGetters: getNextQuestionButtonProps })));
|
|
9
11
|
}
|
|
10
12
|
export default ControlBar;
|
|
@@ -5,17 +5,12 @@ import QuestionDescription from '../QuestionDescription/QuestionDescription';
|
|
|
5
5
|
import { renderImages } from '../../utils';
|
|
6
6
|
import ControlBar from '../ControlBar/ControlBar';
|
|
7
7
|
export default function CoverTypeQuestion() {
|
|
8
|
-
const { state
|
|
8
|
+
const { state } = useContext(QuizContext);
|
|
9
9
|
let question;
|
|
10
10
|
if (state?.quiz.currentQuestion) {
|
|
11
11
|
question = state?.quiz.currentQuestion.next_question;
|
|
12
12
|
}
|
|
13
13
|
const hasImage = question?.images?.primary_url;
|
|
14
|
-
const onNextClick = () => {
|
|
15
|
-
if (nextQuestion) {
|
|
16
|
-
nextQuestion();
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
14
|
if (question) {
|
|
20
15
|
return (React.createElement("div", { className: `
|
|
21
16
|
cio-container${hasImage ? '--with-image' : ''}
|
|
@@ -25,7 +20,7 @@ export default function CoverTypeQuestion() {
|
|
|
25
20
|
React.createElement("div", { className: 'cio-question-content' },
|
|
26
21
|
React.createElement(QuestionTitle, { title: question?.title }),
|
|
27
22
|
React.createElement(QuestionDescription, { description: question.description }),
|
|
28
|
-
React.createElement(ControlBar, {
|
|
23
|
+
React.createElement(ControlBar, { ctaButtonText: question?.cta_text }))));
|
|
29
24
|
}
|
|
30
25
|
return null;
|
|
31
26
|
}
|
|
@@ -1,42 +1,15 @@
|
|
|
1
|
-
import React, { useContext
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
2
|
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
6
|
import ControlBar from '../ControlBar/ControlBar';
|
|
7
|
-
function OpenTextQuestion(
|
|
8
|
-
const {
|
|
9
|
-
const { state, previousQuestion, nextQuestion } = useContext(QuizContext);
|
|
10
|
-
const [openTextInput, setOpenTextInput] = useState(initialValue);
|
|
7
|
+
function OpenTextQuestion() {
|
|
8
|
+
const { state, getOpenTextInputProps } = useContext(QuizContext);
|
|
11
9
|
let question;
|
|
12
10
|
if (state?.quiz.currentQuestion) {
|
|
13
11
|
question = state?.quiz.currentQuestion.next_question;
|
|
14
12
|
}
|
|
15
|
-
const onChangeHandler = (e) => {
|
|
16
|
-
setOpenTextInput(e.target.value);
|
|
17
|
-
if (userDefinedHandler) {
|
|
18
|
-
userDefinedHandler(e);
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
const onNextClick = () => {
|
|
22
|
-
if (nextQuestion && openTextInput) {
|
|
23
|
-
nextQuestion(openTextInput);
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
const onKeyDownHandler = (e) => {
|
|
27
|
-
const { key } = e;
|
|
28
|
-
if (key === 'Enter') {
|
|
29
|
-
onNextClick();
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
useEffect(() => {
|
|
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;
|
|
37
|
-
setOpenTextInput(openTextAnswer);
|
|
38
|
-
}
|
|
39
|
-
}, [state, initialValue]);
|
|
40
13
|
if (question) {
|
|
41
14
|
const hasImage = question?.images?.primary_url;
|
|
42
15
|
return (React.createElement("div", { className: `
|
|
@@ -47,8 +20,8 @@ function OpenTextQuestion(props) {
|
|
|
47
20
|
React.createElement("div", { className: 'cio-question-content' },
|
|
48
21
|
React.createElement(QuestionTitle, { title: question.title }),
|
|
49
22
|
React.createElement(QuestionDescription, { description: question.description }),
|
|
50
|
-
React.createElement("input", {
|
|
51
|
-
React.createElement(ControlBar, {
|
|
23
|
+
getOpenTextInputProps && React.createElement("input", { ...getOpenTextInputProps() }),
|
|
24
|
+
React.createElement(ControlBar, { ctaButtonText: question?.cta_text }))));
|
|
52
25
|
}
|
|
53
26
|
return null;
|
|
54
27
|
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
2
|
import RedoSVG from './RedoSVG';
|
|
3
|
+
import QuizContext from '../CioQuiz/context';
|
|
3
4
|
function RedoButton(props) {
|
|
4
5
|
const { redoText = 'Redo Quiz', disabled, ...rest } = props;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const { getResetQuizButtonProps } = useContext(QuizContext);
|
|
7
|
+
if (getResetQuizButtonProps) {
|
|
8
|
+
return (
|
|
9
|
+
// eslint-disable-next-line react/button-has-type
|
|
10
|
+
React.createElement("button", { ...rest, ...getResetQuizButtonProps() },
|
|
11
|
+
React.createElement(RedoSVG, null),
|
|
12
|
+
React.createElement("span", null, redoText)));
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
8
15
|
}
|
|
9
16
|
export default RedoButton;
|
|
@@ -3,21 +3,9 @@ import ResultCtaButton from '../ResultCtaButton/ResultCtaButton';
|
|
|
3
3
|
import QuizContext from '../CioQuiz/context';
|
|
4
4
|
export default function ResultCard(props) {
|
|
5
5
|
const { result, salePriceKey, regularPriceKey, resultPosition } = props;
|
|
6
|
-
const {
|
|
6
|
+
const { customClickItemCallback, getQuizResultButtonProps, getQuizResultLinkProps } = useContext(QuizContext);
|
|
7
7
|
const salePrice = salePriceKey && result?.data?.[salePriceKey];
|
|
8
8
|
const regularPrice = regularPriceKey && result?.data?.[regularPriceKey];
|
|
9
|
-
const clickHandler = () => {
|
|
10
|
-
if (resultClick) {
|
|
11
|
-
resultClick(result, resultPosition);
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
const keyDownHandler = (event) => {
|
|
15
|
-
if (event?.key === ' ' || event?.key === 'Enter') {
|
|
16
|
-
if (resultClick) {
|
|
17
|
-
resultClick(result, resultPosition);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
9
|
const resultCardContent = () => (React.createElement(React.Fragment, null,
|
|
22
10
|
React.createElement("div", { className: 'cio-result-card-image' },
|
|
23
11
|
React.createElement("img", { src: result.data?.image_url, alt: 'product' })),
|
|
@@ -30,8 +18,8 @@ export default function ResultCard(props) {
|
|
|
30
18
|
regularPrice && (React.createElement("span", { className: `cio-result-card-regular-price${salePrice ? '--strike-through' : ''}` },
|
|
31
19
|
"$",
|
|
32
20
|
regularPrice))))));
|
|
33
|
-
const resultCardContentWithoutLink = () => (React.createElement("div", {
|
|
34
|
-
const resultCardContentWithLink = () => (React.createElement("a", { className: 'cio-result-card-anchor',
|
|
21
|
+
const resultCardContentWithoutLink = () => getQuizResultButtonProps && (React.createElement("div", { ...getQuizResultButtonProps({ result, position: resultPosition, type: 'button' }) }, resultCardContent()));
|
|
22
|
+
const resultCardContentWithLink = () => getQuizResultLinkProps && (React.createElement("a", { className: 'cio-result-card-anchor', ...getQuizResultLinkProps({ result, position: resultPosition, type: 'link' }) }, resultCardContent()));
|
|
35
23
|
return (React.createElement("div", { className: 'cio-result-card' },
|
|
36
24
|
!customClickItemCallback ? resultCardContentWithLink() : resultCardContentWithoutLink(),
|
|
37
25
|
React.createElement(ResultCtaButton, { item: result, price: salePrice || regularPrice })));
|
|
@@ -7,7 +7,7 @@ import Results from '../Results/Results';
|
|
|
7
7
|
export default function ResultContainer(props) {
|
|
8
8
|
const { options } = props;
|
|
9
9
|
const { resultCardSalePriceKey, resultCardRegularPriceKey } = options;
|
|
10
|
-
const { state
|
|
10
|
+
const { state } = useContext(QuizContext);
|
|
11
11
|
const zeroResults = !state?.quiz.results?.response?.results?.length;
|
|
12
12
|
const resultsTitle = zeroResults ? 'Oops, there are no results' : 'Here are your results';
|
|
13
13
|
if (state?.quiz.results) {
|
|
@@ -15,7 +15,7 @@ export default function ResultContainer(props) {
|
|
|
15
15
|
React.createElement("h1", { className: 'cio-results-title' }, resultsTitle),
|
|
16
16
|
React.createElement("div", { className: 'cio-results-filter-and-redo-container' },
|
|
17
17
|
React.createElement(ResultFilters, null),
|
|
18
|
-
React.createElement(RedoButton,
|
|
18
|
+
React.createElement(RedoButton, null)),
|
|
19
19
|
!zeroResults && (React.createElement(Results, { resultCardSalePriceKey: resultCardSalePriceKey, resultCardRegularPriceKey: resultCardRegularPriceKey })),
|
|
20
20
|
zeroResults && React.createElement(ZeroResults, null)));
|
|
21
21
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
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
|
-
|
|
4
|
+
const { item, price } = props;
|
|
5
|
+
const { getAddToCartButtonProps } = useContext(QuizContext);
|
|
6
|
+
if (getAddToCartButtonProps) {
|
|
7
|
+
return (
|
|
8
|
+
// eslint-disable-next-line react/button-has-type
|
|
9
|
+
React.createElement("button", { ...getAddToCartButtonProps(item, price) }, "Add to Cart"));
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
11
12
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ResultCtaButton from '../ResultCtaButton/ResultCtaButton';
|
|
3
|
+
// const flattenRequestFilters: any = (result: any, key: any) => {
|
|
4
|
+
// return Object.keys(result).reduce((res: any, el: any) => {
|
|
5
|
+
// if (el === key) {
|
|
6
|
+
// if (typeof result[el] === 'string') {
|
|
7
|
+
// return [...res, result[el]];
|
|
8
|
+
// } else if (Array.isArray(result[el])) {
|
|
9
|
+
// return [...res, ...result[el]];
|
|
10
|
+
// }
|
|
11
|
+
// }
|
|
12
|
+
// if (typeof result[el] === 'object' && result[el] !== null) {
|
|
13
|
+
// return [...res, ...flattenRequestFilters(result[el], key)]
|
|
14
|
+
// }
|
|
15
|
+
// return res;
|
|
16
|
+
// }, [])
|
|
17
|
+
// }
|
|
18
|
+
// Code used to flatten facets and extract the key. TBD if we are using it for facet pills
|
|
19
|
+
export default function ResultHeroCard(props) {
|
|
20
|
+
const { heroItem } = props;
|
|
21
|
+
return (React.createElement("div", { className: 'cio-hero-card' },
|
|
22
|
+
React.createElement("img", { className: 'cio-hero-card-image', src: heroItem?.data?.image_url, alt: 'product' }),
|
|
23
|
+
React.createElement("div", { className: 'cio-hero-card-contents' },
|
|
24
|
+
React.createElement("div", { className: 'cio-hero-card-title' }, "Especially Curated For You!"),
|
|
25
|
+
React.createElement("h2", { className: 'cio-hero-card-item-name' }, heroItem?.value),
|
|
26
|
+
React.createElement("div", { className: 'cio-hero-card-item-price' },
|
|
27
|
+
"$",
|
|
28
|
+
heroItem?.data?.price),
|
|
29
|
+
React.createElement("p", { className: 'cio-hero-card-item-description' }),
|
|
30
|
+
React.createElement(ResultCtaButton, { item: [heroItem] }))));
|
|
31
|
+
}
|
|
@@ -1,63 +1,20 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
2
|
import QuestionTitle from '../QuestionTitle/QuestionTitle';
|
|
3
3
|
import QuestionDescription from '../QuestionDescription/QuestionDescription';
|
|
4
4
|
import QuizContext from '../CioQuiz/context';
|
|
5
5
|
import { renderImages } from '../../utils';
|
|
6
|
-
import { QuestionTypes } from '../CioQuiz/actions';
|
|
7
6
|
import ControlBar from '../ControlBar/ControlBar';
|
|
7
|
+
import { QuestionTypes } from '../CioQuiz/actions';
|
|
8
8
|
function SelectTypeQuestion() {
|
|
9
|
-
const { state,
|
|
9
|
+
const { state, getSelectInputProps } = useContext(QuizContext);
|
|
10
10
|
let question;
|
|
11
|
-
let type;
|
|
12
11
|
let hasImages = false;
|
|
13
12
|
let instructions;
|
|
14
13
|
if (state?.quiz.currentQuestion) {
|
|
15
14
|
question = state.quiz.currentQuestion.next_question;
|
|
16
|
-
type = question.type;
|
|
17
15
|
hasImages = question.options.some((option) => option.images);
|
|
18
|
-
instructions = type === QuestionTypes.MultipleSelect && 'Select one or more options';
|
|
16
|
+
instructions = question.type === QuestionTypes.MultipleSelect && 'Select one or more options';
|
|
19
17
|
}
|
|
20
|
-
const [selected, setSelected] = useState({});
|
|
21
|
-
const isDisabled = Object.keys(selected).length === 0;
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
if (state?.quiz.currentQuestion?.next_question?.type) {
|
|
24
|
-
const nextQuestionId = state.quiz.currentQuestion.next_question.id;
|
|
25
|
-
const answers = state.answers?.inputs?.[nextQuestionId] || [];
|
|
26
|
-
const prevSelected = {};
|
|
27
|
-
answers?.forEach((answer) => {
|
|
28
|
-
prevSelected[Number(answer)] = true;
|
|
29
|
-
});
|
|
30
|
-
setSelected(prevSelected);
|
|
31
|
-
}
|
|
32
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
33
|
-
}, [state?.quiz.currentQuestion?.next_question.id]);
|
|
34
|
-
const toggleIdSelected = (id) => {
|
|
35
|
-
if (type === QuestionTypes.SingleSelect && nextQuestion) {
|
|
36
|
-
setSelected({ [id]: true });
|
|
37
|
-
nextQuestion([id.toString()]);
|
|
38
|
-
}
|
|
39
|
-
else if (type === QuestionTypes.MultipleSelect) {
|
|
40
|
-
if (selected[id]) {
|
|
41
|
-
const newState = { ...selected };
|
|
42
|
-
delete newState[id];
|
|
43
|
-
setSelected(newState);
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
setSelected({ ...selected, [id]: true });
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
const onOptionKeyDown = (event, id) => {
|
|
51
|
-
if (event?.key === ' ' || event?.key === 'Enter') {
|
|
52
|
-
toggleIdSelected(id);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
const onNextClick = () => {
|
|
56
|
-
if (nextQuestion && !isDisabled && state?.quiz.currentQuestion) {
|
|
57
|
-
const selectedAnswers = Object.keys(selected).filter((key) => selected[Number(key)]);
|
|
58
|
-
nextQuestion(selectedAnswers);
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
18
|
if (question) {
|
|
62
19
|
return (React.createElement("div", { className: 'cio-select-question-container', "data-question-key": question.key },
|
|
63
20
|
React.createElement("div", { className: 'cio-select-question-text' },
|
|
@@ -66,16 +23,10 @@ function SelectTypeQuestion() {
|
|
|
66
23
|
instructions && React.createElement("div", { className: 'cio-select-question-instructions' }, instructions),
|
|
67
24
|
React.createElement("div", { className: `${!hasImages
|
|
68
25
|
? 'cio-question-options-container-text-only'
|
|
69
|
-
: 'cio-question-options-container'}` }, question?.options?.map((option) => (React.createElement("div", {
|
|
70
|
-
? 'cio-question-option-container-text-only'
|
|
71
|
-
: 'cio-question-option-container'} ${selected[option.id] ? 'selected' : ''}`, "data-question-option-key": option.key, onClick: () => {
|
|
72
|
-
toggleIdSelected(option.id);
|
|
73
|
-
}, onKeyDown: (event) => {
|
|
74
|
-
onOptionKeyDown(event, option.id);
|
|
75
|
-
}, role: 'button', tabIndex: 0, key: option.id },
|
|
26
|
+
: 'cio-question-options-container'}` }, question?.options?.map((option) => getSelectInputProps && (React.createElement("div", { ...getSelectInputProps(option) },
|
|
76
27
|
option.images ? renderImages(option.images, 'cio-question-option-image') : '',
|
|
77
28
|
React.createElement("div", { className: 'cio-question-option-value' }, option?.value))))),
|
|
78
|
-
React.createElement(ControlBar, {
|
|
29
|
+
React.createElement(ControlBar, { ctaButtonText: question?.cta_text || 'Continue' })));
|
|
79
30
|
}
|
|
80
31
|
return null;
|
|
81
32
|
}
|
|
@@ -5,16 +5,28 @@ export default function SessionPromptModal({ continueSession, resetStoredState,
|
|
|
5
5
|
resetStoredState();
|
|
6
6
|
setShowSessionPrompt(false);
|
|
7
7
|
};
|
|
8
|
-
if (showSessionPrompt)
|
|
8
|
+
if (showSessionPrompt) {
|
|
9
|
+
const getContinueSessionButtonProps = () => ({
|
|
10
|
+
className: 'cio-question-cta-button',
|
|
11
|
+
onClick: () => {
|
|
12
|
+
continueSession();
|
|
13
|
+
setShowSessionPrompt(false);
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
const getResetSession = () => ({
|
|
17
|
+
className: 'cio-question-cta-button',
|
|
18
|
+
onClick: () => {
|
|
19
|
+
resetStoredState();
|
|
20
|
+
setShowSessionPrompt(false);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
9
23
|
return (React.createElement("div", { className: 'cio-session-prompt-modal', role: 'presentation', onClick: onNoClickHandler },
|
|
10
24
|
React.createElement("div", { className: 'cio-session-prompt-container' },
|
|
11
25
|
React.createElement("div", { className: 'cio-session-prompt-content', role: 'presentation', onClick: (e) => e.stopPropagation() },
|
|
12
26
|
React.createElement("div", null, "Do you want to continue where you left off?"),
|
|
13
27
|
React.createElement("div", { className: 'cio-session-prompt-controls-container' },
|
|
14
|
-
React.createElement(CTAButton, { ctaText: 'No', type: 'button',
|
|
15
|
-
React.createElement(CTAButton, { type: 'button', ctaText: 'Yes',
|
|
16
|
-
|
|
17
|
-
setShowSessionPrompt(false);
|
|
18
|
-
} }))))));
|
|
28
|
+
React.createElement(CTAButton, { ctaText: 'No', type: 'button', propsGetters: getResetSession }),
|
|
29
|
+
React.createElement(CTAButton, { type: 'button', ctaText: 'Yes', propsGetters: getContinueSessionButtonProps }))))));
|
|
30
|
+
}
|
|
19
31
|
return null;
|
|
20
32
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
2
|
import CTAButton from '../CTAButton/CTAButton';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import QuizContext from '../CioQuiz/context';
|
|
4
|
+
function ZeroResults() {
|
|
5
|
+
const { getResetQuizButtonProps } = useContext(QuizContext);
|
|
5
6
|
return (React.createElement("div", { className: 'cio-zero-results' },
|
|
6
7
|
React.createElement("h3", { className: 'cio-zero-results-subtitle' }, "Sorry, it seems like we couldn\u2019t find results based on your answers."),
|
|
7
8
|
React.createElement("p", { className: 'cio-zero-results-description' }, "This is embarrassing \uD83D\uDE22. It might be that some of the questions are not properly set up from our end. Would you give us another try?"),
|
|
8
|
-
React.createElement(CTAButton, { ctaText: 'Try Again',
|
|
9
|
+
React.createElement(CTAButton, { ctaText: 'Try Again', propsGetters: getResetQuizButtonProps })));
|
|
9
10
|
}
|
|
10
11
|
export default ZeroResults;
|
package/lib/mjs/constants.js
CHANGED
|
@@ -20,6 +20,41 @@ export const componentDescription = `- import \`CioQuiz\` to render in your JSX.
|
|
|
20
20
|
|
|
21
21
|
> Note: \`cioJsClient\` refers to an instance of the [constructorio-client-javascript](https://www.npmjs.com/package/@constructor-io/constructorio-client-javascript)
|
|
22
22
|
`;
|
|
23
|
+
export const hookDescription = `- import \`useCioQuiz\` and call this custom hook in a functional component.
|
|
24
|
+
- This hook leaves rendering logic up to you, while handling:
|
|
25
|
+
- state management
|
|
26
|
+
- data fetching
|
|
27
|
+
- keyboard navigation
|
|
28
|
+
- mouse interactions
|
|
29
|
+
- focus and submit event handling
|
|
30
|
+
- Since the markup is controlled by you, the default styles might not be applied if you have a different DOM structure
|
|
31
|
+
- To use this hook, an \`apiKey\` and \`quizId\` are required, and \`resultsPageOptions\` must be passed to the \`useCioQuiz\` hook to configure behavior. All other values are optional.
|
|
32
|
+
- use the <a href="https://kentcdodds.com/blog/how-to-give-rendering-control-to-users-with-prop-getters" target="__blank">prop getters</a> and other variables returned by this hook (below) to leverage the functionality described above with jsx elements in your react component definitions
|
|
33
|
+
|
|
34
|
+
Calling the \`useCioQuiz\` hook returns an object with the following keys:
|
|
35
|
+
|
|
36
|
+
\`\`\`jsx
|
|
37
|
+
const {
|
|
38
|
+
// must be used for a hooks integrations
|
|
39
|
+
state: QuizReturnState, // Quiz state
|
|
40
|
+
events: [{...}], // array of quiz events
|
|
41
|
+
getAddToCartButtonProps: () => ({...})), // prop getter for jsx add to cart button for quiz result,
|
|
42
|
+
getCoverQuestionProps: () => ({...})), // prop getter for jsx quiz cover question,
|
|
43
|
+
getHydrateQuizButtonProps: () => ({...})), // prop getter for jsx hydrate quiz button,
|
|
44
|
+
getNextQuestionButtonProps: () => ({...})), // prop getter for jsx next button to traverse the quiz,
|
|
45
|
+
getPreviousQuestionButtonProps: () => ({...})), // prop getter for jsx back button to traverse the quiz,
|
|
46
|
+
getOpenTextInputProps: () => ({...})), // prop getter for jsx open text input,
|
|
47
|
+
getSelectInputProps: () => ({...})), // prop getter for jsx select input for select type questions,
|
|
48
|
+
getQuizImageProps: () => ({...})), // prop getter for jsx quiz image,
|
|
49
|
+
getQuizResultButtonProps: () => ({...})), // prop getter for jsx result card click as a button,
|
|
50
|
+
getQuizResultLinkProps: () => ({...})), // prop getter for jsx result card click as a link. Should be spread into <a> tags,
|
|
51
|
+
getResetQuizButtonProps: () => ({...})), // prop getter for jsx reset quiz button,
|
|
52
|
+
cioClient, // instance of constructorio-client-javascript
|
|
53
|
+
} = useCioQuiz(args);
|
|
54
|
+
\`\`\`
|
|
55
|
+
|
|
56
|
+
> Note: when we say \`cioClient\`, we are referring to an instance of the [constructorio-client-javascript](https://www.npmjs.com/package/@constructor-io/constructorio-client-javascript)
|
|
57
|
+
`;
|
|
23
58
|
/// //////////////////////////////
|
|
24
59
|
// Storybook Stories
|
|
25
60
|
/// //////////////////////////////
|
|
@@ -41,8 +76,8 @@ In the example below, the \`primaryColor\` prop has been used to override this c
|
|
|
41
76
|
`;
|
|
42
77
|
export var RequestStates;
|
|
43
78
|
(function (RequestStates) {
|
|
44
|
-
RequestStates[
|
|
45
|
-
RequestStates[
|
|
46
|
-
RequestStates[
|
|
47
|
-
RequestStates[
|
|
79
|
+
RequestStates["Stale"] = "STALE";
|
|
80
|
+
RequestStates["Loading"] = "LOADING";
|
|
81
|
+
RequestStates["Success"] = "SUCCESS";
|
|
82
|
+
RequestStates["Error"] = "ERROR";
|
|
48
83
|
})(RequestStates || (RequestStates = {}));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
|
-
const useConsoleErrors = (
|
|
2
|
+
const useConsoleErrors = (quizOptions) => {
|
|
3
|
+
const { quizId, resultsPageOptions } = quizOptions;
|
|
3
4
|
useEffect(() => {
|
|
4
5
|
if (!quizId) {
|
|
5
6
|
// eslint-disable-next-line no-console
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import useSelectInputProps from './useSelectInputProps';
|
|
3
|
+
import useCoverQuestionProps from './useCoverQuestionProps';
|
|
4
|
+
import useOpenTextInputProps from './useOpenTextInputProps';
|
|
5
|
+
import useNextQuestionButtonProps from './useNextQuestionButtonProps';
|
|
6
|
+
import usePreviousQuestionButtonProps from './usePreviousQuestionButtonProps';
|
|
7
|
+
const usePropsGetters = (quizEvents, quizApiState, quizLocalState) => {
|
|
8
|
+
const { quizAnswerChanged, nextQuestion, previousQuestion, resetQuiz, hydrateQuiz, addToCart, resultClick, } = quizEvents;
|
|
9
|
+
const getOpenTextInputProps = useOpenTextInputProps(quizAnswerChanged, nextQuestion, quizApiState.quizCurrentQuestion?.next_question, quizLocalState.answerInputs);
|
|
10
|
+
const getCoverQuestionProps = useCoverQuestionProps(quizAnswerChanged, quizApiState.quizCurrentQuestion?.next_question);
|
|
11
|
+
const getSelectInputProps = useSelectInputProps(quizAnswerChanged, nextQuestion, quizApiState.quizCurrentQuestion?.next_question, quizLocalState.answerInputs);
|
|
12
|
+
const getNextQuestionButtonProps = useNextQuestionButtonProps(nextQuestion, quizApiState, quizLocalState);
|
|
13
|
+
const getPreviousQuestionButtonProps = usePreviousQuestionButtonProps(quizApiState, previousQuestion);
|
|
14
|
+
const getResetQuizButtonProps = useCallback(() => ({
|
|
15
|
+
className: 'cio-question-redo-button',
|
|
16
|
+
type: 'button',
|
|
17
|
+
onClick: () => resetQuiz(),
|
|
18
|
+
}), [resetQuiz]);
|
|
19
|
+
const getHydrateQuizButtonProps = useCallback(() => ({
|
|
20
|
+
className: '',
|
|
21
|
+
type: 'button',
|
|
22
|
+
onClick: () => hydrateQuiz(),
|
|
23
|
+
}), [hydrateQuiz]);
|
|
24
|
+
const getAddToCartButtonProps = useCallback((result, price) => ({
|
|
25
|
+
className: 'cio-result-card-cta-button',
|
|
26
|
+
type: 'button',
|
|
27
|
+
onClick: (e) => addToCart(e, result, price),
|
|
28
|
+
}), [addToCart]);
|
|
29
|
+
const quizResultClickDown = useCallback((event, result, position) => {
|
|
30
|
+
if (event?.key === ' ' || event?.key === 'Enter') {
|
|
31
|
+
if (resultClick) {
|
|
32
|
+
resultClick(result, position);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}, [resultClick]);
|
|
36
|
+
const getQuizResultButtonProps = useCallback(({ result, position }) => ({
|
|
37
|
+
className: 'cio-result-card-container',
|
|
38
|
+
role: 'button',
|
|
39
|
+
tabIndex: 0,
|
|
40
|
+
onClick: () => resultClick(result, position),
|
|
41
|
+
onKeyDown: (event) => quizResultClickDown(event, result, position),
|
|
42
|
+
}), [quizResultClickDown, resultClick]);
|
|
43
|
+
const getQuizResultLinkProps = useCallback(({ result, position }) => ({
|
|
44
|
+
href: result.data?.url,
|
|
45
|
+
onClick: () => resultClick(result, position),
|
|
46
|
+
onKeyDown: (event) => quizResultClickDown(event, result, position),
|
|
47
|
+
}), [quizResultClickDown, resultClick]);
|
|
48
|
+
const getQuizImageProps = useCallback(() => ({
|
|
49
|
+
src: quizApiState.quizCurrentQuestion?.next_question?.images?.primary_url,
|
|
50
|
+
alt: quizApiState.quizCurrentQuestion?.next_question?.images?.primary_alt,
|
|
51
|
+
}), [quizApiState.quizCurrentQuestion]);
|
|
52
|
+
const getSelectQuestionImageProps = useCallback((option) => ({
|
|
53
|
+
className: 'cio-question-option-image',
|
|
54
|
+
src: option?.images?.primary_url,
|
|
55
|
+
alt: option?.images?.primary_alt,
|
|
56
|
+
}), []);
|
|
57
|
+
return {
|
|
58
|
+
getOpenTextInputProps,
|
|
59
|
+
getNextQuestionButtonProps,
|
|
60
|
+
getPreviousQuestionButtonProps,
|
|
61
|
+
getQuizImageProps,
|
|
62
|
+
getSelectQuestionImageProps,
|
|
63
|
+
getSelectInputProps,
|
|
64
|
+
getCoverQuestionProps,
|
|
65
|
+
getResetQuizButtonProps,
|
|
66
|
+
getHydrateQuizButtonProps,
|
|
67
|
+
getAddToCartButtonProps,
|
|
68
|
+
getQuizResultButtonProps,
|
|
69
|
+
getQuizResultLinkProps,
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
export default usePropsGetters;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useCallback, useEffect } from 'react';
|
|
2
|
+
export default function useCoverQuestionProps(setQuizAnswers, currentQuestionData) {
|
|
3
|
+
useEffect(() => {
|
|
4
|
+
if (currentQuestionData?.type === 'cover') {
|
|
5
|
+
setQuizAnswers('');
|
|
6
|
+
}
|
|
7
|
+
}, [setQuizAnswers, currentQuestionData]);
|
|
8
|
+
const getCoverQuestionProps = useCallback(() => ({}), []);
|
|
9
|
+
return getCoverQuestionProps;
|
|
10
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
export default function useNextQuestionButtonProps(nextQuestion, quizApiState, quizLocalState) {
|
|
3
|
+
const getNextQuestionButtonProps = useCallback(() => {
|
|
4
|
+
const currentQuestionId = quizApiState.quizCurrentQuestion?.next_question.id;
|
|
5
|
+
let buttonDisabled;
|
|
6
|
+
if (currentQuestionId && !quizApiState.quizCurrentQuestion?.isCoverQuestion) {
|
|
7
|
+
buttonDisabled =
|
|
8
|
+
!quizLocalState.answerInputs[currentQuestionId]?.value ||
|
|
9
|
+
!quizLocalState.answerInputs[currentQuestionId]?.value?.length;
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
className: buttonDisabled ? 'cio-question-cta-button disabled' : 'cio-question-cta-button',
|
|
13
|
+
type: 'button',
|
|
14
|
+
onClick: () => {
|
|
15
|
+
nextQuestion();
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}, [quizApiState.quizCurrentQuestion, quizLocalState.answerInputs, nextQuestion]);
|
|
19
|
+
return getNextQuestionButtonProps;
|
|
20
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
2
|
+
export default function useOpenTextInputProps(setQuizAnswers, nextQuestion, currentQuestionData, answerInputs) {
|
|
3
|
+
const [input, setInput] = useState('');
|
|
4
|
+
const onChangeHandler = useCallback((e) => {
|
|
5
|
+
setInput(e.target.value);
|
|
6
|
+
}, []);
|
|
7
|
+
const onKeyDownHandler = useCallback((e) => {
|
|
8
|
+
const { key } = e;
|
|
9
|
+
if (key === 'Enter') {
|
|
10
|
+
if (input && currentQuestionData?.type === 'open') {
|
|
11
|
+
nextQuestion();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}, [currentQuestionData?.type, input, nextQuestion]);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (currentQuestionData?.type === 'open') {
|
|
17
|
+
setQuizAnswers(input);
|
|
18
|
+
}
|
|
19
|
+
}, [input, currentQuestionData?.type, currentQuestionData?.id, setQuizAnswers]);
|
|
20
|
+
// Reset input for new open text questions
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (answerInputs && currentQuestionData?.id) {
|
|
23
|
+
const questionAnswer = answerInputs[currentQuestionData.id]?.value;
|
|
24
|
+
if (questionAnswer) {
|
|
25
|
+
setInput(questionAnswer);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
setInput('');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
32
|
+
}, [currentQuestionData?.id]);
|
|
33
|
+
const getOpenTextInputProps = useCallback(() => ({
|
|
34
|
+
className: 'cio-question-input-text',
|
|
35
|
+
placeholder: currentQuestionData?.input_placeholder || 'Answer here...',
|
|
36
|
+
value: input,
|
|
37
|
+
onChange: onChangeHandler,
|
|
38
|
+
onKeyDown: onKeyDownHandler,
|
|
39
|
+
}),
|
|
40
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
41
|
+
[currentQuestionData?.input_placeholder, currentQuestionData?.id, input, onKeyDownHandler]);
|
|
42
|
+
return getOpenTextInputProps;
|
|
43
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
export default function usePreviousQuestionButtonProps(quizApiState, previousQuestion) {
|
|
3
|
+
const getPreviousQuestionButtonProps = useCallback(() => {
|
|
4
|
+
const buttonDisabled = quizApiState.quizCurrentQuestion?.isFirstQuestion;
|
|
5
|
+
return {
|
|
6
|
+
title: 'Quiz Back Button',
|
|
7
|
+
role: 'button',
|
|
8
|
+
className: `cio-question-back-button ${buttonDisabled ? 'disabled' : ''}`,
|
|
9
|
+
type: 'button',
|
|
10
|
+
onClick: () => previousQuestion(),
|
|
11
|
+
};
|
|
12
|
+
}, [quizApiState, previousQuestion]);
|
|
13
|
+
return getPreviousQuestionButtonProps;
|
|
14
|
+
}
|