@constructor-io/constructorio-ui-quizzes 1.9.9 → 1.10.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/README.md +2 -3
- package/dist/constructorio-ui-quizzes-bundled.js +14 -14
- package/lib/cjs/components/CioQuiz/actions.js +1 -0
- package/lib/cjs/components/CioQuiz/index.js +10 -6
- package/lib/cjs/components/CioQuiz/quizApiReducer.js +4 -1
- package/lib/cjs/components/ResultContainer/ResultContainer.js +25 -19
- package/lib/cjs/components/ShareButton/ShareButton.js +17 -0
- package/lib/cjs/components/ShareButton/ShareSVG.js +11 -0
- package/lib/cjs/components/ShareResultsModal/AlertCircleSVG.js +11 -0
- package/lib/cjs/components/ShareResultsModal/CheckMarkCircleSVG.js +10 -0
- package/lib/cjs/components/ShareResultsModal/CloseSVG.js +10 -0
- package/lib/cjs/components/ShareResultsModal/EmailField.js +50 -0
- package/lib/cjs/components/ShareResultsModal/LinkField.js +30 -0
- package/lib/cjs/components/ShareResultsModal/ShareResultsModal.js +24 -0
- package/lib/cjs/constants.js +3 -42
- package/lib/cjs/hooks/useConsoleErrors.js +4 -8
- package/lib/cjs/hooks/usePropsGetters/index.js +2 -0
- package/lib/cjs/hooks/useQueryParams.js +31 -0
- package/lib/cjs/hooks/useQuiz.js +3 -3
- package/lib/cjs/hooks/useQuizEvents/index.js +10 -5
- package/lib/cjs/hooks/useQuizEvents/useQuizResetClick.js +15 -2
- package/lib/cjs/hooks/useQuizState/useQuizApiState.js +27 -2
- package/lib/cjs/hooks/useShareResultsLink.js +18 -0
- package/lib/cjs/index.js +3 -1
- package/lib/cjs/services/index.js +3 -1
- package/lib/cjs/stories/Quiz/argTypes.js +258 -7
- package/lib/cjs/utils.js +1 -0
- package/lib/cjs/version.js +1 -1
- package/lib/mjs/components/CioQuiz/actions.js +1 -0
- package/lib/mjs/components/CioQuiz/index.js +10 -6
- package/lib/mjs/components/CioQuiz/quizApiReducer.js +9 -0
- package/lib/mjs/components/ResultContainer/ResultContainer.js +25 -19
- package/lib/mjs/components/ShareButton/ShareButton.js +14 -0
- package/lib/mjs/components/ShareButton/ShareSVG.js +7 -0
- package/lib/mjs/components/ShareResultsModal/AlertCircleSVG.js +7 -0
- package/lib/mjs/components/ShareResultsModal/CheckMarkCircleSVG.js +6 -0
- package/lib/mjs/components/ShareResultsModal/CloseSVG.js +6 -0
- package/lib/mjs/components/ShareResultsModal/EmailField.js +46 -0
- package/lib/mjs/components/ShareResultsModal/LinkField.js +26 -0
- package/lib/mjs/components/ShareResultsModal/ShareResultsModal.js +20 -0
- package/lib/mjs/constants.js +2 -41
- package/lib/mjs/hooks/useConsoleErrors.js +4 -8
- package/lib/mjs/hooks/usePropsGetters/index.js +2 -0
- package/lib/mjs/hooks/useQueryParams.js +29 -0
- package/lib/mjs/hooks/useQuiz.js +3 -3
- package/lib/mjs/hooks/useQuizEvents/index.js +10 -5
- package/lib/mjs/hooks/useQuizEvents/useQuizResetClick.js +14 -2
- package/lib/mjs/hooks/useQuizState/useQuizApiState.js +28 -3
- package/lib/mjs/hooks/useShareResultsLink.js +14 -0
- package/lib/mjs/index.js +1 -0
- package/lib/mjs/services/index.js +1 -0
- package/lib/mjs/stories/Quiz/argTypes.js +257 -6
- package/lib/mjs/utils.js +1 -0
- package/lib/mjs/version.js +1 -1
- package/lib/styles.css +194 -2
- package/lib/types/components/CioQuiz/actions.d.ts +7 -3
- package/lib/types/components/CioQuiz/context.d.ts +2 -1
- package/lib/types/components/CioQuiz/quizApiReducer.d.ts +2 -2
- package/lib/types/components/ResultCard/ResultCard.d.ts +2 -2
- package/lib/types/components/ResultContainer/ResultContainer.d.ts +5 -3
- package/lib/types/components/Results/Results.d.ts +2 -2
- package/lib/types/components/ShareButton/ShareButton.d.ts +7 -0
- package/lib/types/components/ShareButton/ShareSVG.d.ts +2 -0
- package/lib/types/components/ShareResultsModal/AlertCircleSVG.d.ts +2 -0
- package/lib/types/components/ShareResultsModal/CheckMarkCircleSVG.d.ts +2 -0
- package/lib/types/components/ShareResultsModal/CloseSVG.d.ts +2 -0
- package/lib/types/components/ShareResultsModal/EmailField.d.ts +6 -0
- package/lib/types/components/ShareResultsModal/LinkField.d.ts +6 -0
- package/lib/types/components/ShareResultsModal/ShareResultsModal.d.ts +9 -0
- package/lib/types/constants.d.ts +1 -5
- package/lib/types/hooks/usePropsGetters/index.d.ts +2 -1
- package/lib/types/hooks/useQueryParams.d.ts +7 -0
- package/lib/types/hooks/useQuizEvents/useQuizNextClick.d.ts +2 -2
- package/lib/types/hooks/useQuizEvents/useQuizResetClick.d.ts +9 -2
- package/lib/types/hooks/useQuizEvents/useQuizSkipClick.d.ts +2 -2
- package/lib/types/hooks/useShareResultsLink.d.ts +2 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/services/index.d.ts +6 -5
- package/lib/types/stories/Quiz/argTypes.d.ts +253 -6
- package/lib/types/types.d.ts +32 -14
- package/lib/types/utils.d.ts +2 -1
- package/lib/types/version.d.ts +1 -1
- package/package.json +2 -2
- package/lib/cjs/stories/Quiz/tests/mocks.js +0 -170
- package/lib/mjs/stories/Quiz/tests/mocks.js +0 -198
- package/lib/types/stories/Quiz/tests/mocks.d.ts +0 -44
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import CheckMarkCircleSVG from './CheckMarkCircleSVG';
|
|
3
|
+
import AlertCircleSVG from './AlertCircleSVG';
|
|
4
|
+
export default function LinkField({ url }) {
|
|
5
|
+
const [isCopied, setIsCopied] = useState(false);
|
|
6
|
+
const [isError, setIsError] = useState(false);
|
|
7
|
+
return (React.createElement("div", { className: 'cio-share-results-feature-group' },
|
|
8
|
+
React.createElement("div", { className: 'cio-share-results-description' }, "Share by link"),
|
|
9
|
+
React.createElement("div", { className: 'cio-share-results-button-group' },
|
|
10
|
+
React.createElement("input", { className: 'cio-share-results-link-input', value: url, disabled: true }),
|
|
11
|
+
React.createElement("button", { className: 'cio-share-results-share-button', type: 'button', onClick: () => {
|
|
12
|
+
try {
|
|
13
|
+
navigator.clipboard.writeText(url);
|
|
14
|
+
setIsCopied(true);
|
|
15
|
+
}
|
|
16
|
+
catch (_e) {
|
|
17
|
+
setIsError(true);
|
|
18
|
+
}
|
|
19
|
+
} }, "Copy link")),
|
|
20
|
+
isCopied && (React.createElement("div", { className: 'cio-share-results-notification' },
|
|
21
|
+
React.createElement(CheckMarkCircleSVG, null),
|
|
22
|
+
React.createElement("div", null, "Link copied to clipboard"))),
|
|
23
|
+
isError && (React.createElement("div", { className: 'cio-share-results-notification' },
|
|
24
|
+
React.createElement(AlertCircleSVG, null),
|
|
25
|
+
React.createElement("div", null, "Sorry, there was an error copying. Please try again.")))));
|
|
26
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import CloseSVG from './CloseSVG';
|
|
3
|
+
import LinkField from './LinkField';
|
|
4
|
+
import EmailField from './EmailField';
|
|
5
|
+
import useShareResultsLink from '../../hooks/useShareResultsLink';
|
|
6
|
+
export default function ShareResultsModal({ onClose, quizState, onEmailResults, }) {
|
|
7
|
+
const url = useShareResultsLink(quizState);
|
|
8
|
+
return (React.createElement("div", { className: 'cio-share-results-modal', role: 'presentation', onClick: onClose },
|
|
9
|
+
React.createElement("div", { className: 'cio-share-results-container' },
|
|
10
|
+
React.createElement("div", { className: 'cio-share-results-content', onClick: (e) => e.stopPropagation(), role: 'presentation' },
|
|
11
|
+
React.createElement("div", { className: 'cio-share-results-header' },
|
|
12
|
+
React.createElement("div", { className: 'cio-share-results-title' }, "Share results"),
|
|
13
|
+
React.createElement("button", { onClick: onClose, type: 'button', className: 'cio-modal-close-button', "aria-label": 'Close button' },
|
|
14
|
+
React.createElement(CloseSVG, null))),
|
|
15
|
+
React.createElement("div", null, onEmailResults
|
|
16
|
+
? 'Share or save your quiz results through email or using the link below.'
|
|
17
|
+
: 'Share or save your quiz results with this link.'),
|
|
18
|
+
onEmailResults && React.createElement(EmailField, { onSubmit: (email) => onEmailResults({ email, url }) }),
|
|
19
|
+
React.createElement(LinkField, { url: url })))));
|
|
20
|
+
}
|
package/lib/mjs/constants.js
CHANGED
|
@@ -6,35 +6,7 @@ export const quizSessionStateKey = 'constructorIOQuizState';
|
|
|
6
6
|
/// //////////////////////////////
|
|
7
7
|
// Storybook Folder Descriptions
|
|
8
8
|
/// //////////////////////////////
|
|
9
|
-
export const
|
|
10
|
-
- This component handles state management, data fetching, and rendering logic.
|
|
11
|
-
- To use this component, \`quizId\`, \`resultsPageOptions\`, and one of \`apiKey\` or \`cioJsClient\` are required.
|
|
12
|
-
- \`resultsPageOptions\` lets you configure the results page
|
|
13
|
-
- \`onAddToCartClick\` is a callback function that will be called when the "Add to cart" button is clicked
|
|
14
|
-
- \`onAddToFavoritesClick\` is an optional callback function that will be called when the "Add To Favorites" heart icon is clicked
|
|
15
|
-
- \`onQuizResultClick\` is an optional callback function that will be called when the result card is clicked. The default behavior is redirecting the user to the item's URL
|
|
16
|
-
- \`onQuizResultsLoaded\` is an optional callback function that will be called when the quiz results are loaded
|
|
17
|
-
- \`resultCardRegularPriceKey\` is a parameter that specifies the metadata field name for the regular price
|
|
18
|
-
- \`resultCardSalePriceKey\` is an optional parameter that specifies the metadata field name for the sale price
|
|
19
|
-
- \`resultCardRatingCountKey\` is an optional parameter that specifies the metadata field name for the ratings count
|
|
20
|
-
- \`resultCardRatingScoreKey\` is an optional parameter that specifies the metadata field name for the ratings score
|
|
21
|
-
- \`renderResultCardPriceDetails\` is an optional render function to render custom prices section in result card
|
|
22
|
-
- \`numResultsToDisplay\` is an optional parameter that determines how many results should be displayed on results page
|
|
23
|
-
- \`callbacks\` lets you pass callback functions that will be called on certain actions
|
|
24
|
-
- \`onQuizNextQuestion\` is an optional callback function that will be called when user moves to the next question
|
|
25
|
-
- \`onQuizSkipQuestion\` is an optional callback function that will be called when user skips a question
|
|
26
|
-
- \`sessionStateOptions\` lets you configure the session modal behavior
|
|
27
|
-
- \`showSessionModal\` is a boolean used to decide whether to show the session modal. The default behavior is to show the session modal
|
|
28
|
-
- \`showSessionModalOnResults\` is a boolean to decide whether to show the session modal after reaching the results page. The default behavior is to not show the session modal
|
|
29
|
-
- \`sessionStateKey\` is a custom string that will be used as a session storage key
|
|
30
|
-
- \`questionsPageOptions\` lets you configure the question page
|
|
31
|
-
- \`skipQuestionButtonText\` is an optional string that will be used as the skip button text
|
|
32
|
-
- Use different props to configure the behavior of this component.
|
|
33
|
-
- The following stories show how different props affect the component's behavior
|
|
34
|
-
|
|
35
|
-
> Note: \`cioJsClient\` refers to an instance of the [constructorio-client-javascript](https://www.npmjs.com/package/@constructor-io/constructorio-client-javascript)
|
|
36
|
-
`;
|
|
37
|
-
export const hookDescription = `- import \`useCioQuiz\` and call this custom hook in a functional component.
|
|
9
|
+
export const hookDescription = `- Import \`useCioQuiz\` and call this custom hook in a functional component.
|
|
38
10
|
- This hook leaves rendering logic up to you, while handling:
|
|
39
11
|
- state management
|
|
40
12
|
- data fetching
|
|
@@ -43,7 +15,7 @@ export const hookDescription = `- import \`useCioQuiz\` and call this custom hoo
|
|
|
43
15
|
- focus and submit event handling
|
|
44
16
|
- Since the markup is controlled by you, the default styles might not be applied if you have a different DOM structure
|
|
45
17
|
- 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.
|
|
46
|
-
-
|
|
18
|
+
- 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
|
|
47
19
|
|
|
48
20
|
Calling the \`useCioQuiz\` hook returns an object with the following keys:
|
|
49
21
|
|
|
@@ -75,9 +47,6 @@ const {
|
|
|
75
47
|
// Storybook Stories
|
|
76
48
|
/// //////////////////////////////
|
|
77
49
|
export const basicDescription = `Pass an \`apiKey\` and a \`quizId\` to request questions and quiz results from Constructor's servers`;
|
|
78
|
-
export const cioJsClientDescription = `If you are already using an instance of the \`ConstructorIOClient\`, you can pass a \`cioJsClient\` instead of an \`apiKey\` to request results from Constructor's servers
|
|
79
|
-
|
|
80
|
-
> Note: \`cioJsClient\` refers to an instance of the [constructorio-client-javascript](https://www.npmjs.com/package/@constructor-io/constructorio-client-javascript)`;
|
|
81
50
|
export const smallContainerDescription = `If you are using the provided styles, CioQuiz component will respect the height and width of its parent container and use responsive styles based on the parent container's dimensions`;
|
|
82
51
|
export const changePrimaryColorDescription = `
|
|
83
52
|
If you would like to use a different primary color, pass a \`primaryColor\` string in RGB format ('R, G, B').
|
|
@@ -90,14 +59,6 @@ In the example below, the \`primaryColor\` prop has been used to override this c
|
|
|
90
59
|
|
|
91
60
|
> Advanced Option: Instead of passing a primaryColor prop, you can also override \`--primary-color-h\`, \`--primary-color-s\`, and \`--primary-color-l\` CSS variables within a \`.cio-quiz\` container element. If explicitly given a value in your CSS, then the values of these variables will be used as the HSL values for your quiz.
|
|
92
61
|
`;
|
|
93
|
-
export const callbacksDescription = `Pass an \`apiKey\`, a \`quizId\`, and \`callbacks\``;
|
|
94
|
-
export const favoritesDescription = `
|
|
95
|
-
Add \`const [favorites, setFavorites] = useState([]);\` or equivalent to manage the favorite items' state.
|
|
96
|
-
|
|
97
|
-
Pass favorites as an array to \`favoriteItems\` in \`resultsPageOptions\` as a prop to CioQuiz.
|
|
98
|
-
|
|
99
|
-
Pass a callback function to \`onAddToFavoritesClick\` in \`resultsPageOptions\` as a prop to CioQuiz to handle favorites state changes to update \`favorites\` array.
|
|
100
|
-
`;
|
|
101
62
|
export var RequestStates;
|
|
102
63
|
(function (RequestStates) {
|
|
103
64
|
RequestStates["Stale"] = "STALE";
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
const useConsoleErrors = (quizOptions) => {
|
|
3
|
-
const { quizId, resultsPageOptions } = quizOptions;
|
|
3
|
+
const { quizId, resultsPageOptions, callbacks } = quizOptions;
|
|
4
4
|
useEffect(() => {
|
|
5
5
|
if (!quizId) {
|
|
6
6
|
// eslint-disable-next-line no-console
|
|
7
7
|
console.error('quizId is a required field of type string');
|
|
8
8
|
}
|
|
9
|
-
if (!
|
|
9
|
+
if (!callbacks?.onAddToCartClick) {
|
|
10
10
|
// eslint-disable-next-line no-console
|
|
11
|
-
console.error('
|
|
11
|
+
console.error('callbacks.onAddToCartClick is a required field of type function');
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
// eslint-disable-next-line no-console
|
|
15
|
-
console.error('resultsPageOptions.onAddToCartClick is a required field of type function');
|
|
16
|
-
}
|
|
17
|
-
}, [quizId, resultsPageOptions, resultsPageOptions?.onAddToCartClick]);
|
|
13
|
+
}, [quizId, resultsPageOptions, callbacks?.onAddToCartClick]);
|
|
18
14
|
};
|
|
19
15
|
export default useConsoleErrors;
|
|
@@ -19,6 +19,7 @@ const usePropsGetters = (quizEvents, quizApiState, quizLocalState, favoriteItems
|
|
|
19
19
|
type: 'button',
|
|
20
20
|
onClick: () => resetQuiz(),
|
|
21
21
|
}), [resetQuiz]);
|
|
22
|
+
const getShareResultsButtonProps = useCallback(() => ({ className: 'cio-question-share-results-button' }), []);
|
|
22
23
|
const getHydrateQuizButtonProps = useCallback(() => ({
|
|
23
24
|
className: '',
|
|
24
25
|
type: 'button',
|
|
@@ -67,6 +68,7 @@ const usePropsGetters = (quizEvents, quizApiState, quizLocalState, favoriteItems
|
|
|
67
68
|
getSelectInputProps,
|
|
68
69
|
getCoverQuestionProps,
|
|
69
70
|
getResetQuizButtonProps,
|
|
71
|
+
getShareResultsButtonProps,
|
|
70
72
|
getHydrateQuizButtonProps,
|
|
71
73
|
getAddToCartButtonProps,
|
|
72
74
|
getAddToFavoritesButtonProps,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
const useQueryParams = () => {
|
|
3
|
+
const getParsedQueryParam = (queryName) => {
|
|
4
|
+
const queryParams = new URLSearchParams(window.location.search);
|
|
5
|
+
const queryParam = queryParams.get(queryName);
|
|
6
|
+
if (!queryParam)
|
|
7
|
+
return [];
|
|
8
|
+
return queryParam?.split(',');
|
|
9
|
+
};
|
|
10
|
+
const queryItems = getParsedQueryParam('items');
|
|
11
|
+
const queryAttributes = getParsedQueryParam('attributes');
|
|
12
|
+
const isSharedResultsQuery = !!queryItems.length && !!queryAttributes.length;
|
|
13
|
+
const removeSharedResultsQueryParams = useCallback(() => {
|
|
14
|
+
const updatedUrl = new URL(window.location.href);
|
|
15
|
+
updatedUrl.searchParams.delete('items');
|
|
16
|
+
updatedUrl.searchParams.delete('attributes');
|
|
17
|
+
if (!updatedUrl.searchParams.toString().length) {
|
|
18
|
+
updatedUrl.search = '';
|
|
19
|
+
}
|
|
20
|
+
window.history.replaceState({}, '', updatedUrl.toString());
|
|
21
|
+
}, []);
|
|
22
|
+
return {
|
|
23
|
+
queryItems,
|
|
24
|
+
queryAttributes,
|
|
25
|
+
isSharedResultsQuery,
|
|
26
|
+
removeSharedResultsQueryParams,
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export default useQueryParams;
|
package/lib/mjs/hooks/useQuiz.js
CHANGED
|
@@ -6,8 +6,8 @@ import usePrimaryColorStyles from './usePrimaryColorStyles';
|
|
|
6
6
|
import useQuizEvents from './useQuizEvents';
|
|
7
7
|
import useQuizState from './useQuizState';
|
|
8
8
|
const useQuiz = (quizOptions) => {
|
|
9
|
-
const { apiKey, cioJsClient, primaryColor, resultsPageOptions
|
|
10
|
-
// Log console errors for required parameters quizId
|
|
9
|
+
const { apiKey, cioJsClient, primaryColor, resultsPageOptions } = quizOptions;
|
|
10
|
+
// Log console errors for required parameters quizId
|
|
11
11
|
useConsoleErrors(quizOptions);
|
|
12
12
|
// Quiz Cio Client
|
|
13
13
|
const cioClient = useCioClient({ apiKey, cioJsClient });
|
|
@@ -18,7 +18,7 @@ const useQuiz = (quizOptions) => {
|
|
|
18
18
|
// Props getters
|
|
19
19
|
const { quizApiState, quizLocalState, quizSessionStorageState } = quizState;
|
|
20
20
|
const { skipToResults } = quizSessionStorageState;
|
|
21
|
-
const propGetters = usePropsGetters(quizEvents, quizApiState, quizLocalState, favoriteItems);
|
|
21
|
+
const propGetters = usePropsGetters(quizEvents, quizApiState, quizLocalState, resultsPageOptions?.favoriteItems);
|
|
22
22
|
const primaryColorStyles = usePrimaryColorStyles(primaryColor);
|
|
23
23
|
useEffect(() => {
|
|
24
24
|
if (skipToResults)
|
|
@@ -11,13 +11,13 @@ import useQuizAddToFavorites from './useQuizAddToFavorites';
|
|
|
11
11
|
import useQuizSkipClick from './useQuizSkipClick';
|
|
12
12
|
const useQuizEvents = (quizOptions, cioClient, quizState) => {
|
|
13
13
|
const { quizApiState, dispatchLocalState, dispatchApiState, quizLocalState, quizSessionStorageState, } = quizState;
|
|
14
|
-
const {
|
|
15
|
-
const { onAddToCartClick, onQuizResultClick, onQuizResultsLoaded,
|
|
14
|
+
const { callbacks } = quizOptions;
|
|
15
|
+
const { onAddToCartClick, onAddToFavoritesClick, onQuizNextQuestion, onQuizResultClick, onQuizResultsLoaded, onQuizSkipQuestion, } = callbacks || {};
|
|
16
16
|
// Quiz answer change
|
|
17
17
|
const quizAnswerChanged = useQuizAnswerChangeHandler(quizApiState, dispatchLocalState);
|
|
18
18
|
// Quiz Next button click callback
|
|
19
|
-
const nextQuestion = useQuizNextClick(quizApiState, quizLocalState, dispatchLocalState,
|
|
20
|
-
const skipQuestion = useQuizSkipClick(quizApiState, quizLocalState, dispatchLocalState,
|
|
19
|
+
const nextQuestion = useQuizNextClick(quizApiState, quizLocalState, dispatchLocalState, onQuizNextQuestion);
|
|
20
|
+
const skipQuestion = useQuizSkipClick(quizApiState, quizLocalState, dispatchLocalState, onQuizSkipQuestion);
|
|
21
21
|
// Quiz Back button click callback
|
|
22
22
|
const previousQuestion = useQuizBackClick(quizApiState, dispatchLocalState);
|
|
23
23
|
// Quiz result add to cart callback
|
|
@@ -28,7 +28,12 @@ const useQuizEvents = (quizOptions, cioClient, quizState) => {
|
|
|
28
28
|
// Quiz results loaded event
|
|
29
29
|
useQuizResultsLoaded(cioClient, quizApiState, onQuizResultsLoaded);
|
|
30
30
|
// Quiz reset
|
|
31
|
-
const resetQuiz = useQuizResetClick(
|
|
31
|
+
const resetQuiz = useQuizResetClick({
|
|
32
|
+
resetQuizSessionStorageState: resetQuizSessionStorageState(quizSessionStorageState.key),
|
|
33
|
+
dispatchLocalState,
|
|
34
|
+
dispatchApiState,
|
|
35
|
+
quizResults: quizApiState.quizResults,
|
|
36
|
+
});
|
|
32
37
|
// Quiz rehydrate
|
|
33
38
|
const hydrateQuizLocalState = useHydrateQuizLocalState(quizSessionStorageState.key, dispatchLocalState);
|
|
34
39
|
return {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
import { QuestionTypes, QuizAPIActionTypes, } from '../../components/CioQuiz/actions';
|
|
3
|
-
|
|
3
|
+
import useQueryParams from '../useQueryParams';
|
|
4
|
+
const useQuizResetClick = (props) => {
|
|
5
|
+
const { resetQuizSessionStorageState, dispatchLocalState, dispatchApiState, quizResults } = props;
|
|
6
|
+
const { removeSharedResultsQueryParams, isSharedResultsQuery } = useQueryParams();
|
|
4
7
|
const quizResetClickHandler = useCallback(() => {
|
|
5
8
|
if (quizResults) {
|
|
6
9
|
dispatchLocalState({
|
|
@@ -10,8 +13,17 @@ const useQuizResetClick = (resetQuizSessionStorageState, dispatchLocalState, dis
|
|
|
10
13
|
type: QuizAPIActionTypes.RESET_QUIZ,
|
|
11
14
|
});
|
|
12
15
|
resetQuizSessionStorageState();
|
|
16
|
+
if (isSharedResultsQuery)
|
|
17
|
+
removeSharedResultsQueryParams();
|
|
13
18
|
}
|
|
14
|
-
}, [
|
|
19
|
+
}, [
|
|
20
|
+
dispatchLocalState,
|
|
21
|
+
dispatchApiState,
|
|
22
|
+
resetQuizSessionStorageState,
|
|
23
|
+
quizResults,
|
|
24
|
+
removeSharedResultsQueryParams,
|
|
25
|
+
isSharedResultsQuery,
|
|
26
|
+
]);
|
|
15
27
|
return quizResetClickHandler;
|
|
16
28
|
};
|
|
17
29
|
export default useQuizResetClick;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { useEffect, useReducer } from 'react';
|
|
2
2
|
import { QuestionTypes, QuizAPIActionTypes, } from '../../components/CioQuiz/actions';
|
|
3
3
|
import apiReducer, { initialState, } from '../../components/CioQuiz/quizApiReducer';
|
|
4
|
-
import { getNextQuestion, getQuizResults } from '../../services';
|
|
4
|
+
import { getNextQuestion, getQuizResults, getBrowseResultsForItemIds } from '../../services';
|
|
5
|
+
import useQueryParams from '../useQueryParams';
|
|
5
6
|
const useQuizApiState = (quizOptions, cioClient, quizLocalState, skipToResults, dispatchLocalState
|
|
6
7
|
// eslint-disable-next-line max-params
|
|
7
8
|
) => {
|
|
8
9
|
const [quizApiState, dispatchApiState] = useReducer(apiReducer, initialState);
|
|
9
10
|
const { quizId, quizVersionId: quizVersionIdProp, resultsPageOptions } = quizOptions;
|
|
11
|
+
const { queryItems, queryAttributes, isSharedResultsQuery } = useQueryParams();
|
|
10
12
|
const dispatchQuizResults = async () => {
|
|
11
13
|
try {
|
|
12
14
|
const quizResults = await getQuizResults(cioClient, quizId, {
|
|
@@ -33,12 +35,29 @@ const useQuizApiState = (quizOptions, cioClient, quizLocalState, skipToResults,
|
|
|
33
35
|
});
|
|
34
36
|
}
|
|
35
37
|
};
|
|
38
|
+
const dispatchSharedQuizResults = async () => {
|
|
39
|
+
try {
|
|
40
|
+
const quizResults = await getBrowseResultsForItemIds(cioClient, queryItems);
|
|
41
|
+
dispatchApiState({
|
|
42
|
+
type: QuizAPIActionTypes.SET_QUIZ_SHARED_RESULTS,
|
|
43
|
+
payload: { quizResults: { ...quizResults, attributes: queryAttributes } },
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
dispatchApiState({
|
|
48
|
+
type: QuizAPIActionTypes.SET_IS_ERROR,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
36
52
|
useEffect(() => {
|
|
37
53
|
(async () => {
|
|
38
54
|
dispatchApiState({
|
|
39
55
|
type: QuizAPIActionTypes.SET_IS_LOADING,
|
|
40
56
|
});
|
|
41
|
-
if (
|
|
57
|
+
if (isSharedResultsQuery) {
|
|
58
|
+
await dispatchSharedQuizResults();
|
|
59
|
+
}
|
|
60
|
+
else if (skipToResults) {
|
|
42
61
|
await dispatchQuizResults();
|
|
43
62
|
}
|
|
44
63
|
else {
|
|
@@ -81,7 +100,13 @@ const useQuizApiState = (quizOptions, cioClient, quizLocalState, skipToResults,
|
|
|
81
100
|
}
|
|
82
101
|
})();
|
|
83
102
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
84
|
-
}, [
|
|
103
|
+
}, [
|
|
104
|
+
cioClient,
|
|
105
|
+
quizId,
|
|
106
|
+
quizLocalState.answers,
|
|
107
|
+
resultsPageOptions?.numResultsToDisplay,
|
|
108
|
+
isSharedResultsQuery,
|
|
109
|
+
]);
|
|
85
110
|
return {
|
|
86
111
|
quizApiState,
|
|
87
112
|
dispatchApiState,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export default function useShareResultsLink(quizState) {
|
|
2
|
+
const urlObj = new URL(window.location.href);
|
|
3
|
+
const existingParams = urlObj.searchParams;
|
|
4
|
+
if (!quizState.results?.response?.results) {
|
|
5
|
+
throw new Error("Can't generate share link without results");
|
|
6
|
+
}
|
|
7
|
+
existingParams.set('items', quizState.results.response.results
|
|
8
|
+
.filter((item) => item.data?.id)
|
|
9
|
+
.map((item) => item.data.id)
|
|
10
|
+
.join(',') || '');
|
|
11
|
+
existingParams.set('attributes', quizState.selectedOptionsWithAttributes?.map((option) => option).join(',') || '');
|
|
12
|
+
const value = urlObj.toString();
|
|
13
|
+
return value;
|
|
14
|
+
}
|
package/lib/mjs/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import CioQuiz from './components/CioQuiz';
|
|
2
2
|
// Hook
|
|
3
3
|
export { default as useCioQuiz } from './hooks/useQuiz';
|
|
4
|
+
export { default as useShareResultsLink } from './hooks/useShareResultsLink';
|
|
4
5
|
// Questions Components
|
|
5
6
|
export { default as QuizQuestions } from './components/QuizQuestions/index';
|
|
6
7
|
export { default as OpenTextQuestion } from './components/OpenTextTypeQuestion/OpenTextTypeQuestion';
|
|
@@ -13,6 +13,7 @@ export const getCioClient = (apiKey) => {
|
|
|
13
13
|
};
|
|
14
14
|
export const getNextQuestion = (cioClient, quizId, parameters) => cioClient?.quizzes.getQuizNextQuestion(quizId, parameters);
|
|
15
15
|
export const getQuizResults = async (cioClient, quizId, parameters) => cioClient?.quizzes.getQuizResults(quizId, parameters);
|
|
16
|
+
export const getBrowseResultsForItemIds = async (cioClient, itemIds) => cioClient?.browse.getBrowseResultsForItemIds(itemIds);
|
|
16
17
|
// Tracking requests
|
|
17
18
|
export const trackQuizResultsLoaded = (cioClient, quizResults) => {
|
|
18
19
|
const { quiz_id, quiz_session_id, quiz_version_id, result_id, request, response } = quizResults;
|
|
@@ -1,32 +1,283 @@
|
|
|
1
1
|
// eslint-disable-next-line
|
|
2
2
|
export const argTypes = {
|
|
3
|
+
quizId: {
|
|
4
|
+
description: 'ID of the quiz',
|
|
5
|
+
control: {
|
|
6
|
+
type: 'text',
|
|
7
|
+
},
|
|
8
|
+
},
|
|
9
|
+
quizVersionId: {
|
|
10
|
+
description: 'Optional quiz version Id',
|
|
11
|
+
},
|
|
3
12
|
apiKey: {
|
|
4
|
-
|
|
5
|
-
|
|
13
|
+
description: 'Your Constructor API key. Either `apiKey` or `cioJsClient` are required',
|
|
14
|
+
},
|
|
15
|
+
'callbacks.onQuizNextQuestion': {
|
|
16
|
+
description: 'Callback function to be called when the next question is loaded',
|
|
17
|
+
control: false,
|
|
6
18
|
table: {
|
|
19
|
+
subcategory: 'callbacks',
|
|
20
|
+
defaultValue: {
|
|
21
|
+
summary: 'null',
|
|
22
|
+
},
|
|
23
|
+
type: {
|
|
24
|
+
summary: '(question) => void',
|
|
25
|
+
detail: '(question: QuestionWithAnswer) => void',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
'callbacks.onQuizResultsLoaded': {
|
|
30
|
+
description: 'Callback function to be called when the quiz results are loaded',
|
|
31
|
+
control: false,
|
|
32
|
+
table: {
|
|
33
|
+
subcategory: 'callbacks',
|
|
34
|
+
defaultValue: {
|
|
35
|
+
summary: 'null',
|
|
36
|
+
},
|
|
37
|
+
type: {
|
|
38
|
+
summary: '(results) => void',
|
|
39
|
+
detail: '(results: QuizResultDataPartial) => void',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
'callbacks.onQuizResultClick': {
|
|
44
|
+
description: 'Callback function to be called when a quiz result is clicked',
|
|
45
|
+
control: false,
|
|
46
|
+
table: {
|
|
47
|
+
subcategory: 'callbacks',
|
|
48
|
+
defaultValue: {
|
|
49
|
+
summary: 'null',
|
|
50
|
+
},
|
|
51
|
+
type: {
|
|
52
|
+
summary: '(result, position: number) => void',
|
|
53
|
+
detail: '(result: QuizResultDataPartial, position: number) => void',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
'callbacks.onAddToCartClick': {
|
|
58
|
+
description: 'Callback function to be called when the add to cart button is clicked',
|
|
59
|
+
control: false,
|
|
60
|
+
table: {
|
|
61
|
+
subcategory: 'callbacks',
|
|
62
|
+
defaultValue: {
|
|
63
|
+
summary: 'null',
|
|
64
|
+
},
|
|
65
|
+
type: {
|
|
66
|
+
summary: '(result) => void',
|
|
67
|
+
detail: '(result: QuizResultDataPartial) => void',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
'callbacks.onAddToFavoritesClick': {
|
|
72
|
+
description: 'Callback function to be called when the add to favorites button is clicked',
|
|
73
|
+
control: false,
|
|
74
|
+
table: {
|
|
75
|
+
subcategory: 'callbacks',
|
|
76
|
+
defaultValue: {
|
|
77
|
+
summary: 'null',
|
|
78
|
+
},
|
|
79
|
+
type: {
|
|
80
|
+
summary: '(result) => void',
|
|
81
|
+
detail: '(result: QuizResultDataPartial) => void',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
'callbacks.onEmailResults': {
|
|
86
|
+
description: 'Callback function to be called when emailing results.',
|
|
87
|
+
control: false,
|
|
88
|
+
table: {
|
|
89
|
+
subcategory: 'callbacks',
|
|
90
|
+
defaultValue: {
|
|
91
|
+
summary: 'null',
|
|
92
|
+
},
|
|
93
|
+
type: {
|
|
94
|
+
summary: '(args) => void',
|
|
95
|
+
detail: '(args: QuizEmailResults) => void',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
cioJsClient: {
|
|
100
|
+
description: 'Optional custom constructor instance. Either `apiKey` or `cioJsClient` are required',
|
|
101
|
+
},
|
|
102
|
+
primaryColor: {
|
|
103
|
+
description: "RGB value string for quiz primary theme color ie: `'255, 82, 48'`",
|
|
104
|
+
control: {
|
|
105
|
+
type: 'text',
|
|
106
|
+
},
|
|
107
|
+
defaultValue: {
|
|
108
|
+
summary: '35, 71, 199',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
'resultCardOptions.resultCardRegularPriceKey': {
|
|
112
|
+
description: 'Key name for the regular price in the API response',
|
|
113
|
+
control: {
|
|
114
|
+
type: 'text',
|
|
115
|
+
},
|
|
116
|
+
table: {
|
|
117
|
+
subcategory: 'resultCardOptions',
|
|
118
|
+
defaultValue: {
|
|
119
|
+
summary: 'regular_price',
|
|
120
|
+
},
|
|
7
121
|
type: {
|
|
8
122
|
summary: 'string',
|
|
9
123
|
},
|
|
10
124
|
},
|
|
125
|
+
},
|
|
126
|
+
'resultCardOptions.resultCardSalePriceKey': {
|
|
127
|
+
description: 'Key name for the sale price in the API response',
|
|
11
128
|
control: {
|
|
12
129
|
type: 'text',
|
|
13
130
|
},
|
|
131
|
+
table: {
|
|
132
|
+
subcategory: 'resultCardOptions',
|
|
133
|
+
defaultValue: {
|
|
134
|
+
summary: 'sale_price',
|
|
135
|
+
},
|
|
136
|
+
type: {
|
|
137
|
+
summary: 'string',
|
|
138
|
+
},
|
|
139
|
+
},
|
|
14
140
|
},
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
141
|
+
'resultCardOptions.resultCardRatingCountKey': {
|
|
142
|
+
description: 'Key name for the rating count in the API response',
|
|
143
|
+
control: {
|
|
144
|
+
type: 'text',
|
|
145
|
+
},
|
|
18
146
|
table: {
|
|
147
|
+
subcategory: 'resultCardOptions',
|
|
148
|
+
defaultValue: {
|
|
149
|
+
summary: 'rating_count',
|
|
150
|
+
},
|
|
19
151
|
type: {
|
|
20
152
|
summary: 'string',
|
|
21
153
|
},
|
|
22
154
|
},
|
|
155
|
+
},
|
|
156
|
+
'resultCardOptions.resultCardRatingScoreKey': {
|
|
157
|
+
description: 'Key name for the rating score in the API response',
|
|
23
158
|
control: {
|
|
24
159
|
type: 'text',
|
|
25
160
|
},
|
|
161
|
+
table: {
|
|
162
|
+
subcategory: 'resultCardOptions',
|
|
163
|
+
defaultValue: {
|
|
164
|
+
summary: 'rating_score',
|
|
165
|
+
},
|
|
166
|
+
type: {
|
|
167
|
+
summary: 'string',
|
|
168
|
+
},
|
|
169
|
+
},
|
|
26
170
|
},
|
|
27
|
-
|
|
171
|
+
'resultCardOptions.renderResultCardPriceDetails': {
|
|
172
|
+
description: 'Callback function to render result card price details',
|
|
173
|
+
control: false,
|
|
174
|
+
table: {
|
|
175
|
+
subcategory: 'resultCardOptions',
|
|
176
|
+
defaultValue: {
|
|
177
|
+
summary: 'null',
|
|
178
|
+
},
|
|
179
|
+
type: {
|
|
180
|
+
summary: '(result) => JSX.Element',
|
|
181
|
+
detail: '(result: QuizResultDataPartial) => JSX.Element',
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
'resultsPageOptions.numResultsToDisplay': {
|
|
186
|
+
description: 'Number of results to display on the results page',
|
|
187
|
+
control: {
|
|
188
|
+
type: 'number',
|
|
189
|
+
},
|
|
190
|
+
table: {
|
|
191
|
+
subcategory: 'resultsPageOptions',
|
|
192
|
+
defaultValue: {
|
|
193
|
+
summary: 5,
|
|
194
|
+
},
|
|
195
|
+
type: {
|
|
196
|
+
summary: 'number',
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
'resultsPageOptions.favoriteItems': {
|
|
201
|
+
description: 'Array of favorite item IDs',
|
|
202
|
+
control: false,
|
|
203
|
+
table: {
|
|
204
|
+
subcategory: 'resultsPageOptions',
|
|
205
|
+
type: {
|
|
206
|
+
summary: 'string[]',
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
'resultsPageOptions.showShareResultsButton': {
|
|
211
|
+
description: 'Boolean for whether or not to show share results button on the results page',
|
|
212
|
+
control: {
|
|
213
|
+
type: 'boolean',
|
|
214
|
+
},
|
|
215
|
+
table: {
|
|
216
|
+
subcategory: 'resultCardOptions',
|
|
217
|
+
defaultValue: {
|
|
218
|
+
summary: true,
|
|
219
|
+
},
|
|
220
|
+
type: {
|
|
221
|
+
summary: 'boolean',
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
'sessionStateOptions.showSessionModal': {
|
|
226
|
+
description: 'Boolean for whether or not to show session modal to hydrate quiz on the results page',
|
|
227
|
+
control: {
|
|
228
|
+
type: 'boolean',
|
|
229
|
+
},
|
|
230
|
+
table: {
|
|
231
|
+
subcategory: 'sessionStateOptions',
|
|
232
|
+
defaultValue: {
|
|
233
|
+
summary: false,
|
|
234
|
+
},
|
|
235
|
+
type: {
|
|
236
|
+
summary: 'boolean',
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
'sessionStateOptions.showSessionModalOnResults': {
|
|
241
|
+
description: 'Boolean for whether or not to show session modal to hydrate quiz',
|
|
242
|
+
control: {
|
|
243
|
+
type: 'boolean',
|
|
244
|
+
},
|
|
245
|
+
table: {
|
|
246
|
+
subcategory: 'sessionStateOptions',
|
|
247
|
+
defaultValue: {
|
|
248
|
+
summary: false,
|
|
249
|
+
},
|
|
250
|
+
type: {
|
|
251
|
+
summary: 'boolean',
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
'sessionStateOptions.sessionStateKey': {
|
|
256
|
+
description: 'Key name where session storage state is saved',
|
|
28
257
|
control: {
|
|
29
258
|
type: 'text',
|
|
30
259
|
},
|
|
260
|
+
table: {
|
|
261
|
+
subcategory: 'sessionStateOptions',
|
|
262
|
+
defaultValue: {
|
|
263
|
+
summary: 'quizState',
|
|
264
|
+
},
|
|
265
|
+
type: {
|
|
266
|
+
summary: 'string',
|
|
267
|
+
},
|
|
268
|
+
},
|
|
31
269
|
},
|
|
270
|
+
enableHydration: {
|
|
271
|
+
description: 'Boolean for whether or not to hydrate quiz questions and results on page reload',
|
|
272
|
+
defaultValue: {
|
|
273
|
+
summary: 'true',
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
export const docsControls = {
|
|
278
|
+
sort: 'requiredFirst',
|
|
279
|
+
exclude: ['sessionStateOptions', 'callbacks', 'resultsPageOptions', 'resultCardOptions'],
|
|
280
|
+
};
|
|
281
|
+
export const storiesControls = {
|
|
282
|
+
include: ['apiKey', 'quizId', 'quizVersionId', 'primaryColor'],
|
|
32
283
|
};
|