Merge pull request #8901 from scratchfoundation/onboarding-integration
Onboarding integration
6
.github/workflows/ci-cd.yml
vendored
|
@ -68,8 +68,10 @@ jobs:
|
|||
PROJECT_HOST: ${{ secrets.PROJECT_HOST }}
|
||||
STATIC_HOST: ${{ secrets.STATIC_HOST }}
|
||||
SCRATCH_ENV: ${{ vars.SCRATCH_ENV }}
|
||||
SORTING_HAT_HOST: ${{ vars.SORTING_HAT_HOST }}
|
||||
USER_GUIDING_ID: ${{ secrets.USER_GUIDING_ID }}
|
||||
ONBOARDING_TEST_ACTIVE: ${{ vars.ONBOARDING_TEST_ACTIVE }}
|
||||
ONBOARDING_TEST_PROJECT_IDS: ${{ vars.ONBOARDING_TEST_PROJECT_IDS }}
|
||||
ONBOARDING_TESTING_STARTING_DATE: ${{ vars.ONBOARDING_TESTING_STARTING_DATE }}
|
||||
ONBOARDING_TESTING_ENDING_DATE: ${{ vars.ONBOARDING_TESTING_ENDING_DATE }}
|
||||
|
||||
# used by src/template-config.js
|
||||
GTM_ID: ${{ secrets.GTM_ID }}
|
||||
|
|
3788
package-lock.json
generated
|
@ -46,6 +46,7 @@
|
|||
"dependencies": {
|
||||
"bunyan": "1.8.15",
|
||||
"clipboard-copy": "2.0.1",
|
||||
"driver.js": "^1.3.1",
|
||||
"express": "4.21.1",
|
||||
"express-http-proxy": "1.6.3",
|
||||
"lodash.defaults": "4.2.0",
|
||||
|
@ -127,7 +128,7 @@
|
|||
"postcss-loader": "4.3.0",
|
||||
"postcss-simple-vars": "5.0.2",
|
||||
"prop-types": "15.8.1",
|
||||
"query-string": "5.1.1",
|
||||
"query-string": "9.1.0",
|
||||
"react": "16.14.0",
|
||||
"react-dom": "16.14.0",
|
||||
"react-intl": "5.25.1",
|
||||
|
|
69
src/components/journeys/driver-journey/driver-journey.jsx
Normal file
|
@ -0,0 +1,69 @@
|
|||
const React = require('react');
|
||||
const {useState, useEffect, isValidElement} = require('react');
|
||||
const {createPortal} = require('react-dom');
|
||||
const PropTypes = require('prop-types');
|
||||
require('driver.js/dist/driver.css');
|
||||
|
||||
const DriverJourney = ({configProps, driverObj}) => {
|
||||
const [renderState, setRenderState] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
const {steps, ...restConfig} = configProps;
|
||||
const driverSteps = steps.map((step, index) => {
|
||||
const {sectionComponents = {}, callback, ...popoverProps} = step.popover;
|
||||
return {
|
||||
...step,
|
||||
popover: {
|
||||
...popoverProps,
|
||||
onPopoverRender: popover => {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
const portalData = [];
|
||||
for (const [section, component] of Object.entries(
|
||||
sectionComponents
|
||||
)) {
|
||||
if (isValidElement(component)) {
|
||||
popover[section].style.display = 'block';
|
||||
popover[section].innerHTML = '';
|
||||
portalData.push({
|
||||
parentElement: popover[section],
|
||||
childElement: component
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setRenderState({components: portalData, stepIndex: index});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
driverObj.setConfig({...restConfig, steps: driverSteps});
|
||||
|
||||
driverObj.drive();
|
||||
}, [driverObj, configProps]);
|
||||
|
||||
if (!renderState) return null;
|
||||
if (!configProps.steps[renderState.stepIndex]) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderState.components.map(obj =>
|
||||
createPortal(obj.childElement, obj.parentElement)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
DriverJourney.propTypes = {
|
||||
configProps: PropTypes.shape({
|
||||
steps: PropTypes.arrayOf(PropTypes.object)
|
||||
}),
|
||||
driverObj: PropTypes.shape({
|
||||
setConfig: PropTypes.func,
|
||||
drive: PropTypes.func
|
||||
})
|
||||
};
|
||||
|
||||
module.exports = DriverJourney;
|
358
src/components/journeys/editor-journey/editor-journey.jsx
Normal file
|
@ -0,0 +1,358 @@
|
|||
const React = require('react');
|
||||
const {driver} = require('driver.js');
|
||||
const FlexRow = require('../../flex-row/flex-row.jsx');
|
||||
const Button = require('../../forms/button.jsx');
|
||||
const DriverJourney = require('../driver-journey/driver-journey.jsx');
|
||||
const {defineMessages, useIntl} = require('react-intl');
|
||||
const {useMemo, useState, useCallback} = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const {triggerAnalyticsEvent} = require('../../../lib/onboarding.js');
|
||||
|
||||
require('./editor-journey.scss');
|
||||
|
||||
const messages = defineMessages({
|
||||
createStepTitle: {
|
||||
id: 'gui.journey.controls.create',
|
||||
defaultMessage: 'Create',
|
||||
description: 'Create step title'
|
||||
},
|
||||
projectGenreStepTitle: {
|
||||
id: 'gui.journey.controls.choose.projectGenre',
|
||||
defaultMessage: 'What do you whant to create?',
|
||||
description: 'Choose project genre step title'
|
||||
},
|
||||
typeStepTitle: {
|
||||
id: 'gui.journey.controls.choose.type',
|
||||
defaultMessage: 'Which type?',
|
||||
description: 'Choose project type step title'
|
||||
},
|
||||
startStepTitle: {
|
||||
id: 'gui.journey.controls.choose.start',
|
||||
defaultMessage: 'How do you want to start?',
|
||||
description: 'Choose way to start step title'
|
||||
},
|
||||
gameButtonText: {
|
||||
id: 'gui.journey.controls.game',
|
||||
defaultMessage: 'Game',
|
||||
description: 'Game button text'
|
||||
},
|
||||
animiationButtonText: {
|
||||
id: 'gui.journey.controls.animation',
|
||||
defaultMessage: 'Animation',
|
||||
description: 'Animation button text'
|
||||
},
|
||||
musicButtonText: {
|
||||
id: 'gui.journey.controls.music',
|
||||
defaultMessage: 'Music',
|
||||
description: 'Music button text'
|
||||
},
|
||||
clickerGameButtonText: {
|
||||
id: 'gui.journey.controls.game.clicker',
|
||||
defaultMessage: 'Clicker Game',
|
||||
description: 'Clicker game button text'
|
||||
},
|
||||
pongGameButtonText: {
|
||||
id: 'gui.journey.controls.game.pong',
|
||||
defaultMessage: 'Pong Game',
|
||||
description: 'Pong game button text'
|
||||
},
|
||||
characterAnimationButtonText: {
|
||||
id: 'gui.journey.controls.animation.character',
|
||||
defaultMessage: 'Animate a character',
|
||||
description: 'Animate a character button text'
|
||||
},
|
||||
flyAnimationButtonText: {
|
||||
id: 'gui.journey.controls.animation.fly',
|
||||
defaultMessage: 'Make it fly',
|
||||
description: 'Make it fly animation button text'
|
||||
},
|
||||
recordSoundButtonText: {
|
||||
id: 'gui.journey.controls.music.record',
|
||||
defaultMessage: 'Record a sound',
|
||||
description: 'Record a sound button text'
|
||||
},
|
||||
makeMusicButtonText: {
|
||||
id: 'gui.journey.controls.music.make',
|
||||
defaultMessage: 'Make music',
|
||||
description: 'Make music button text'
|
||||
},
|
||||
tutorialButtonText: {
|
||||
id: 'gui.journey.controls.tutorial',
|
||||
defaultMessage: 'Tutorial',
|
||||
description: 'Tutorial button text'
|
||||
},
|
||||
starterProjectButtonText: {
|
||||
id: 'gui.journey.controls.starterProject',
|
||||
defaultMessage: 'Starter project',
|
||||
description: 'Starter project button text'
|
||||
},
|
||||
onMyOwnButtonText: {
|
||||
id: 'gui.journey.controls.onMyOwn',
|
||||
defaultMessage: 'On my own',
|
||||
description: 'On my own button text'
|
||||
}
|
||||
});
|
||||
|
||||
const STEP_NAMES = [
|
||||
'pick-genre-step',
|
||||
'game-step',
|
||||
'animation-step',
|
||||
'music-step',
|
||||
'clicker-game-step',
|
||||
'pong-game-step',
|
||||
'animate-character-step',
|
||||
'make-fly-animation-step',
|
||||
'record-sound-step',
|
||||
'make-music-step'
|
||||
];
|
||||
|
||||
const projectIds = JSON.parse(process.env.ONBOARDING_TEST_PROJECT_IDS);
|
||||
|
||||
const tutorialIds = {
|
||||
clicker: 'Make-A-Game',
|
||||
pong: 'pong',
|
||||
animateCharacter: 'Animate-A-Character',
|
||||
makeItFly: 'make-it-fly',
|
||||
recordSound: 'record-a-sound',
|
||||
makeMusic: 'Make-Music'
|
||||
};
|
||||
|
||||
const EditorJourneyDescription = ({title, descriptionData}) => (
|
||||
<>
|
||||
<div className="title">{title}</div>
|
||||
<FlexRow className="description-wrapper">
|
||||
{
|
||||
descriptionData.map((prop, index) => (
|
||||
<FlexRow
|
||||
key={index}
|
||||
className="journey-option"
|
||||
>
|
||||
<img src={prop.imgSrc} />
|
||||
<Button
|
||||
className={'large'}
|
||||
onClick={prop.handleOnClick}
|
||||
>{prop.text}</Button>
|
||||
</FlexRow>
|
||||
))
|
||||
}
|
||||
</FlexRow>
|
||||
</>
|
||||
);
|
||||
|
||||
const EditorJourney = ({onActivateDeck, setCanViewTutorialsHighlight, setShowJourney}) => {
|
||||
const [driverObj] = useState(() => (
|
||||
driver()
|
||||
));
|
||||
const intl = useIntl();
|
||||
|
||||
const pickStep = useCallback((stepNumber, editorJourneyStep) => {
|
||||
triggerAnalyticsEvent({
|
||||
event: 'editor-journey-step',
|
||||
editorJourneyStep: editorJourneyStep
|
||||
});
|
||||
driverObj.moveTo(stepNumber);
|
||||
}, driverObj);
|
||||
|
||||
const createStep = useCallback((projectId, tutorialId) => ({
|
||||
title: intl.formatMessage(messages.createStepTitle),
|
||||
showButtons: ['close'],
|
||||
sectionComponents: {
|
||||
description: <EditorJourneyDescription
|
||||
title={intl.formatMessage(messages.startStepTitle)}
|
||||
descriptionData={[
|
||||
{
|
||||
imgSrc: '/images/onboarding-journeys/Tutorials-Icon.svg',
|
||||
text: intl.formatMessage(messages.tutorialButtonText),
|
||||
handleOnClick: () => {
|
||||
triggerAnalyticsEvent({
|
||||
event: 'editor-journey-step',
|
||||
editorJourneyStep: `${tutorialId}-Open-Tutorial`
|
||||
});
|
||||
onActivateDeck(tutorialId);
|
||||
setShowJourney(false);
|
||||
driverObj.destroy();
|
||||
}
|
||||
},
|
||||
{
|
||||
imgSrc: '/images/onboarding-journeys/Starter-Projects-Icon.svg',
|
||||
text: intl.formatMessage(messages.starterProjectButtonText),
|
||||
handleOnClick: () => {
|
||||
location.href = `/projects/${projectId}?showJourney=true`;
|
||||
setShowJourney(false);
|
||||
driverObj.destroy();
|
||||
}
|
||||
},
|
||||
{
|
||||
imgSrc: '/images/onboarding-journeys/On-Own-Icon.svg',
|
||||
text: intl.formatMessage(messages.onMyOwnButtonText),
|
||||
handleOnClick: () => {
|
||||
triggerAnalyticsEvent({
|
||||
event: 'editor-journey-step',
|
||||
editorJourneyStep: `${tutorialId}-On-My-Own`
|
||||
});
|
||||
setCanViewTutorialsHighlight(true);
|
||||
setShowJourney(false);
|
||||
driverObj.destroy();
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
}
|
||||
}), [onActivateDeck, setCanViewTutorialsHighlight, setShowJourney, driverObj, intl]);
|
||||
|
||||
const configProps = useMemo(
|
||||
() => ({
|
||||
popoverClass: 'gui-journey',
|
||||
overlayOpacity: 0,
|
||||
onDestroyStarted: () => {
|
||||
const stepName = STEP_NAMES[driverObj.getActiveIndex()] || '';
|
||||
triggerAnalyticsEvent({
|
||||
event: 'editor-journey-step',
|
||||
editorJourneyStep: `${stepName}-closed`
|
||||
});
|
||||
driverObj.destroy();
|
||||
},
|
||||
onDestroyed: () => {
|
||||
setShowJourney(false);
|
||||
},
|
||||
steps: [{
|
||||
popover: {
|
||||
title: intl.formatMessage(messages.createStepTitle),
|
||||
showButtons: ['close'],
|
||||
sectionComponents: {
|
||||
description: <EditorJourneyDescription
|
||||
title={intl.formatMessage(messages.projectGenreStepTitle)}
|
||||
descriptionData={[
|
||||
{
|
||||
imgSrc: '/images/onboarding-journeys/Games-Icon.svg',
|
||||
text: intl.formatMessage(messages.gameButtonText),
|
||||
handleOnClick: () => pickStep(1, 'Games')
|
||||
},
|
||||
{
|
||||
imgSrc: '/images/onboarding-journeys/Animation-Icon.svg',
|
||||
text: intl.formatMessage(messages.animiationButtonText),
|
||||
handleOnClick: () => pickStep(2, 'Animation')
|
||||
},
|
||||
{
|
||||
imgSrc: '/images/onboarding-journeys/Music-Icon.svg',
|
||||
text: intl.formatMessage(messages.musicButtonText),
|
||||
handleOnClick: () => pickStep(3, 'Music')
|
||||
}
|
||||
]}
|
||||
/>
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
popover: {
|
||||
title: intl.formatMessage(messages.createStepTitle),
|
||||
showButtons: ['close'],
|
||||
sectionComponents: {
|
||||
description: <EditorJourneyDescription
|
||||
title={intl.formatMessage(messages.typeStepTitle)}
|
||||
descriptionData={[
|
||||
{
|
||||
imgSrc: '/images/onboarding-journeys/Clicker-Game.jpg',
|
||||
text: intl.formatMessage(messages.clickerGameButtonText),
|
||||
handleOnClick: () => pickStep(4, 'Clicker-Game')
|
||||
},
|
||||
{
|
||||
imgSrc: '/images/onboarding-journeys/Pong-Game.jpg',
|
||||
text: intl.formatMessage(messages.pongGameButtonText),
|
||||
handleOnClick: () => pickStep(5, 'Pong-Game')
|
||||
}
|
||||
]}
|
||||
/>
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
popover: {
|
||||
title: intl.formatMessage(messages.createStepTitle),
|
||||
showButtons: ['close'],
|
||||
sectionComponents: {
|
||||
description: <EditorJourneyDescription
|
||||
title={intl.formatMessage(messages.typeStepTitle)}
|
||||
descriptionData={[
|
||||
{
|
||||
imgSrc: '/images/onboarding-journeys/Character-Animation.jpg',
|
||||
text: intl.formatMessage(messages.characterAnimationButtonText),
|
||||
handleOnClick: () => pickStep(6, 'Character-Animation')
|
||||
},
|
||||
{
|
||||
imgSrc: '/images/onboarding-journeys/Fly-Animation.jpg',
|
||||
text: intl.formatMessage(messages.flyAnimationButtonText),
|
||||
handleOnClick: () => pickStep(7, 'Fly-Animation')
|
||||
}
|
||||
]}
|
||||
/>
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
popover: {
|
||||
title: intl.formatMessage(messages.createStepTitle),
|
||||
showButtons: ['close'],
|
||||
sectionComponents: {
|
||||
description: <EditorJourneyDescription
|
||||
title={intl.formatMessage(messages.typeStepTitle)}
|
||||
descriptionData={[
|
||||
{
|
||||
imgSrc: '/images/onboarding-journeys/Record-Music.jpg',
|
||||
text: intl.formatMessage(messages.recordSoundButtonText),
|
||||
handleOnClick: () => pickStep(8, 'Record-Music')
|
||||
},
|
||||
{
|
||||
imgSrc: '/images/onboarding-journeys/Make-Music.jpg',
|
||||
text: intl.formatMessage(messages.makeMusicButtonText),
|
||||
handleOnClick: () => pickStep(9, 'Make-Music')
|
||||
}
|
||||
]}
|
||||
/>
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
popover: createStep(projectIds.clicker, tutorialIds.clicker)
|
||||
},
|
||||
{
|
||||
popover: createStep(projectIds.pong, tutorialIds.pong)
|
||||
},
|
||||
{
|
||||
popover: createStep(projectIds.animateCharacter, tutorialIds.animateCharacter)
|
||||
},
|
||||
{
|
||||
popover: createStep(projectIds.makeItFly, tutorialIds.makeItFly)
|
||||
},
|
||||
{
|
||||
popover: createStep(projectIds.recordSound, tutorialIds.recordSound)
|
||||
},
|
||||
{
|
||||
popover: createStep(projectIds.makeMusic, tutorialIds.makeMusic)
|
||||
}]}), [driverObj, intl, createStep, setShowJourney]
|
||||
);
|
||||
|
||||
return (
|
||||
<DriverJourney
|
||||
configProps={configProps}
|
||||
driverObj={driverObj}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
EditorJourneyDescription.propTypes = {
|
||||
title: PropTypes.string,
|
||||
descriptionData: PropTypes.arrayOf(PropTypes.shape({
|
||||
imgSrc: PropTypes.string,
|
||||
text: PropTypes.string,
|
||||
handleOnClick: PropTypes.func
|
||||
}))
|
||||
};
|
||||
|
||||
EditorJourney.propTypes = {
|
||||
onActivateDeck: PropTypes.func,
|
||||
setCanViewTutorialsHighlight: PropTypes.func,
|
||||
setShowJourney: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = EditorJourney;
|
65
src/components/journeys/editor-journey/editor-journey.scss
Normal file
|
@ -0,0 +1,65 @@
|
|||
@import "../../../colors";
|
||||
@import "../../../frameless";
|
||||
|
||||
.driver-popover.gui-journey {
|
||||
font-family: "Helvetica Neue", "Helvetica", Arial, sans-serif;
|
||||
max-width: unset;
|
||||
padding: 0;
|
||||
border-radius: 15px;
|
||||
top: 50% !important;
|
||||
left: 50% !important;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
.driver-popover-close-btn {
|
||||
height: 2.5rem;
|
||||
width: 2.5rem;
|
||||
border-radius: 50%;
|
||||
margin: 0.5rem;
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
color: $type-white;
|
||||
background-color: $ui-aqua-dark;
|
||||
}
|
||||
|
||||
.driver-popover-title {
|
||||
padding: 1rem 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
color: $type-white;
|
||||
margin: 0;
|
||||
background-color: $ui-aqua;
|
||||
border-radius: 15px 15px 0 0;
|
||||
}
|
||||
|
||||
.driver-popover-title[style*=block]+.driver-popover-description {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
padding: 1rem 0;
|
||||
font-size: 1.125rem;
|
||||
font-family: "Helvetica Neue", "Helvetica", Arial, sans-serif;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
color: $type-gray;
|
||||
background-color: $ui-light-primary;
|
||||
}
|
||||
|
||||
.description-wrapper {
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
gap: 3rem;
|
||||
margin: 3rem 4rem;
|
||||
|
||||
.journey-option {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
|
||||
img {
|
||||
height: $cols2;
|
||||
}
|
||||
}
|
||||
}
|
83
src/components/journeys/project-journey/project-journey.jsx
Normal file
|
@ -0,0 +1,83 @@
|
|||
const React = require('react');
|
||||
const {driver} = require('driver.js');
|
||||
const DriverJourney = require('../driver-journey/driver-journey.jsx');
|
||||
const {defineMessages, useIntl} = require('react-intl');
|
||||
const {useState} = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
require('./project-journey.scss');
|
||||
|
||||
const messages = defineMessages({
|
||||
playProject: {
|
||||
id: 'project.journey.play',
|
||||
defaultMessage: 'Click the green flag to see what this project does.',
|
||||
description: 'Play project'
|
||||
},
|
||||
remixProject: {
|
||||
id: 'project.journey.remix',
|
||||
defaultMessage: 'Make your own version!',
|
||||
description: 'Remix project'
|
||||
}
|
||||
});
|
||||
|
||||
const ProjectJourney = ({setCanViewProjectJourney, setShouldStopProject}) => {
|
||||
const [driverObj] = useState(() => (
|
||||
driver()
|
||||
));
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
const steps = [{
|
||||
element: 'div[class^="stage_green-flag-overlay-wrapper"] > div',
|
||||
popover: {
|
||||
callback: () => {
|
||||
const greenFlagButton = document.querySelector('div[class^="stage_green-flag-overlay-wrapper"] > div');
|
||||
greenFlagButton.addEventListener('click', () => {
|
||||
setCanViewProjectJourney(false);
|
||||
driverObj.destroy();
|
||||
setTimeout(() => {
|
||||
setShouldStopProject(true);
|
||||
driverObj.drive(1);
|
||||
}, 8000);
|
||||
});
|
||||
},
|
||||
description: intl.formatMessage(messages.playProject)
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '.remix-button',
|
||||
popover: {
|
||||
callback: () => {
|
||||
const remixButton = document.querySelector('.remix-button');
|
||||
remixButton.addEventListener('click', () => {
|
||||
setCanViewProjectJourney(false);
|
||||
driverObj.destroy();
|
||||
});
|
||||
},
|
||||
description: intl.formatMessage(messages.remixProject)
|
||||
}
|
||||
}];
|
||||
|
||||
return (
|
||||
<DriverJourney
|
||||
configProps={{
|
||||
popoverClass: 'project-journey',
|
||||
showButtons: [
|
||||
'close'
|
||||
],
|
||||
onDestroyed: () => {
|
||||
setCanViewProjectJourney(false);
|
||||
},
|
||||
showProgress: false,
|
||||
steps: steps
|
||||
}}
|
||||
driverObj={driverObj}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
ProjectJourney.propTypes = {
|
||||
setCanViewProjectJourney: PropTypes.func,
|
||||
setShouldStopProject: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = ProjectJourney;
|
41
src/components/journeys/project-journey/project-journey.scss
Normal file
|
@ -0,0 +1,41 @@
|
|||
@import "../../../colors";
|
||||
|
||||
.driver-popover.project-journey {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: $ui-purple-dark;
|
||||
|
||||
.driver-popover-close-btn {
|
||||
height: 2.5rem;
|
||||
width: 2.5rem;
|
||||
border-radius: 50%;
|
||||
margin: 0.5rem;
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
color: $type-white;
|
||||
background-color: $ui-purple-dark;
|
||||
}
|
||||
|
||||
.driver-popover-arrow-side-left.driver-popover-arrow {
|
||||
border-left-color: $ui-purple-dark;;
|
||||
}
|
||||
|
||||
.driver-popover-arrow-side-right.driver-popover-arrow {
|
||||
border-right-color: $ui-purple-dark;;
|
||||
}
|
||||
|
||||
.driver-popover-arrow-side-top.driver-popover-arrow {
|
||||
border-top-color: $ui-purple-dark;;
|
||||
}
|
||||
|
||||
.driver-popover-arrow-side-bottom.driver-popover-arrow {
|
||||
border-bottom-color: $ui-purple-dark;;
|
||||
}
|
||||
|
||||
.driver-popover-description {
|
||||
color: $ui-white;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
font-family: "Helvetica Neue", "Helvetica", Arial, sans-serif;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
const React = require('react');
|
||||
const {driver} = require('driver.js');
|
||||
const DriverJourney = require('../driver-journey/driver-journey.jsx');
|
||||
const {defineMessages, useIntl} = require('react-intl');
|
||||
const PropTypes = require('prop-types');
|
||||
const {useState} = require('react');
|
||||
require('./tutorials-highlight.scss');
|
||||
|
||||
const messages = defineMessages({
|
||||
tutorialsHighlight: {
|
||||
id: 'gui.highlight.tutorials',
|
||||
defaultMessage: 'Click here for tutorials',
|
||||
description: 'Tutorials highlight'
|
||||
}
|
||||
});
|
||||
|
||||
const TutorialsHighlight = ({setCanViewTutorialsHighlight}) => {
|
||||
const [driverObj] = useState(() => (
|
||||
driver()
|
||||
));
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
const steps = [{
|
||||
element: '.tutorials-button',
|
||||
popover: {
|
||||
showButtons: ['close'],
|
||||
callback: () => {
|
||||
const tutorialsButton = document.querySelector('.tutorials-button');
|
||||
tutorialsButton.addEventListener('click', () => {
|
||||
setCanViewTutorialsHighlight(false);
|
||||
driverObj.destroy();
|
||||
});
|
||||
},
|
||||
side: 'bottom',
|
||||
description: intl.formatMessage(messages.tutorialsHighlight)
|
||||
}
|
||||
}];
|
||||
|
||||
return (
|
||||
<DriverJourney
|
||||
configProps={{
|
||||
popoverClass: 'tutorials-highlight',
|
||||
showProgress: false,
|
||||
overlayOpacity: 0,
|
||||
onDestroyed: () => {
|
||||
setCanViewTutorialsHighlight(false);
|
||||
},
|
||||
steps: steps
|
||||
}}
|
||||
driverObj={driverObj}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
TutorialsHighlight.propTypes = {
|
||||
setCanViewTutorialsHighlight: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = TutorialsHighlight;
|
|
@ -0,0 +1,41 @@
|
|||
@import "../../../colors";
|
||||
|
||||
.driver-popover.tutorials-highlight {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: $ui-purple-dark;
|
||||
|
||||
.driver-popover-close-btn {
|
||||
height: 2.5rem;
|
||||
width: 2.5rem;
|
||||
border-radius: 50%;
|
||||
margin: 0.5rem;
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
color: $type-white;
|
||||
background-color: $ui-purple-dark;
|
||||
}
|
||||
|
||||
.driver-popover-arrow-side-left.driver-popover-arrow {
|
||||
border-left-color: $ui-purple-dark;;
|
||||
}
|
||||
|
||||
.driver-popover-arrow-side-right.driver-popover-arrow {
|
||||
border-right-color: $ui-purple-dark;;
|
||||
}
|
||||
|
||||
.driver-popover-arrow-side-top.driver-popover-arrow {
|
||||
border-top-color: $ui-purple-dark;;
|
||||
}
|
||||
|
||||
.driver-popover-arrow-side-bottom.driver-popover-arrow {
|
||||
border-bottom-color: $ui-purple-dark;;
|
||||
}
|
||||
|
||||
.driver-popover-description {
|
||||
color: $ui-white;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
font-family: "Helvetica Neue", "Helvetica", Arial, sans-serif;
|
||||
}
|
||||
}
|
92
src/lib/onboarding.js
Normal file
|
@ -0,0 +1,92 @@
|
|||
const ONBOARDING_TESTING_GROUP_A_NAME = 'Displayed Onboarding Journeys';
|
||||
const ONBOARDING_TESTING_GROUP_B_NAME = 'Not Displayed Onboarding Journeys';
|
||||
|
||||
const isBanned = user => user.banned;
|
||||
|
||||
const isAdmin = permissions => permissions.admin;
|
||||
|
||||
const isMuted = permissions => !!Object.keys(permissions.mute_status).length;
|
||||
|
||||
const isDateInOnboardingRange = date => {
|
||||
const dateToCompare = Date.parse(date);
|
||||
const startDate = Date.parse(process.env.ONBOARDING_TESTING_STARTING_DATE);
|
||||
const endDate = Date.parse(process.env.ONBOARDING_TESTING_ENDING_DATE);
|
||||
|
||||
return dateToCompare >= startDate && dateToCompare <= endDate;
|
||||
};
|
||||
|
||||
const isRegisteredInRange = user => {
|
||||
const dateOfJoin = user.dateJoined.split('T')[0];
|
||||
|
||||
return isDateInOnboardingRange(dateOfJoin);
|
||||
};
|
||||
|
||||
const isCurrentDayInRange = () => {
|
||||
const currentDate = new Date().toJSON()
|
||||
.split('T')[0];
|
||||
|
||||
return isDateInOnboardingRange(currentDate);
|
||||
};
|
||||
|
||||
const isUserEligible = (user, permissions) =>
|
||||
Object.keys(user).length !== 0 &&
|
||||
Object.keys(permissions).length !== 0 &&
|
||||
JSON.parse(process.env.ONBOARDING_TEST_ACTIVE) &&
|
||||
isRegisteredInRange(user) &&
|
||||
isCurrentDayInRange() &&
|
||||
!isAdmin(permissions) &&
|
||||
!isMuted(permissions) &&
|
||||
!isBanned(user);
|
||||
|
||||
const calculateAgeGroup = (birthYear, birthMonth) => {
|
||||
const today = new Date();
|
||||
let age = today.getFullYear() - parseInt(birthYear, 10);
|
||||
const monthDiff = today.getMonth() + 1 - parseInt(birthMonth, 10);
|
||||
if (monthDiff < 0) {
|
||||
age--;
|
||||
}
|
||||
|
||||
if (age <= 10) {
|
||||
return '[00-10]';
|
||||
} else if (age <= 16) {
|
||||
return '[11-16]';
|
||||
}
|
||||
return '[17+]';
|
||||
};
|
||||
|
||||
const onboardingTestGroup = user =>
|
||||
(user.id % 2 === 0 ?
|
||||
ONBOARDING_TESTING_GROUP_A_NAME :
|
||||
ONBOARDING_TESTING_GROUP_B_NAME);
|
||||
|
||||
export const shouldDisplayOnboarding = (user, permissions) =>
|
||||
user.id % 2 === 0 && isUserEligible(user, permissions);
|
||||
|
||||
export const triggerAnalyticsEvent = eventVaribles => {
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
||||
window.dataLayer.push({
|
||||
...eventVaribles
|
||||
});
|
||||
};
|
||||
|
||||
export const sendUserProperties = (user, permissions) => {
|
||||
if (!isUserEligible(user, permissions)) {
|
||||
window.dataLayer.push({
|
||||
testGroup: null,
|
||||
ageGroup: null,
|
||||
gender: null
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
||||
const {gender, birthYear, birthMonth} = user;
|
||||
|
||||
window.dataLayer.push({
|
||||
testGroup: onboardingTestGroup(user),
|
||||
ageGroup: calculateAgeGroup(birthYear, birthMonth),
|
||||
gender: gender
|
||||
});
|
||||
};
|
|
@ -1,124 +0,0 @@
|
|||
const api = require('./api');
|
||||
const sample = require('lodash.sample');
|
||||
|
||||
const USER_GUIDING_ID = process.env.USER_GUIDING_ID;
|
||||
const AUTONOMY_SURVEY_ID = 3677;
|
||||
const RELATIONSHIP_SURVEY_ID = 3678;
|
||||
const JOY_SURVEY_ID = 3679;
|
||||
const COMPETENCE_SURVEY_ID = 3676;
|
||||
const EDITOR_INTERACTION_SURVEY_IDS = [COMPETENCE_SURVEY_ID, JOY_SURVEY_ID];
|
||||
|
||||
const CONDITIONS = {condition_list: [
|
||||
'IsLoggedIn',
|
||||
'NotAdmin',
|
||||
'NotMuted'
|
||||
]};
|
||||
|
||||
const USER_GUIDING_SNIPPET = `
|
||||
(function(g, u, i, d, e, s) {
|
||||
g[e] = g[e] || [];
|
||||
var f = u.getElementsByTagName(i)[0];
|
||||
var k = u.createElement(i);
|
||||
k.async = true;
|
||||
k.src = 'https://static.userguiding.com/media/user-guiding-' + s + '-embedded.js';
|
||||
f.parentNode.insertBefore(k, f);
|
||||
if (g[d]) return;
|
||||
var ug = g[d] = {
|
||||
q: []
|
||||
};
|
||||
ug.c = function(n) {
|
||||
return function() {
|
||||
ug.q.push([n, arguments])
|
||||
};
|
||||
};
|
||||
var m = ['previewGuide', 'finishPreview', 'track', 'identify', 'hideChecklist', 'launchChecklist'];
|
||||
for (var j = 0; j < m.length; j += 1) {
|
||||
ug[m[j]] = ug.c(m[j]);
|
||||
}
|
||||
})(window, document, 'script', 'userGuiding', 'userGuidingLayer', '${USER_GUIDING_ID}');
|
||||
`;
|
||||
|
||||
const activateUserGuiding = (userId, callback) => {
|
||||
if (window.userGuiding) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
const userGuidingScript = document.createElement('script');
|
||||
userGuidingScript.innerHTML = USER_GUIDING_SNIPPET;
|
||||
document.head.insertBefore(userGuidingScript, document.head.firstChild);
|
||||
|
||||
window.userGuidingSettings = {disablePageViewAutoCapture: true};
|
||||
|
||||
window.userGuidingLayer.push({
|
||||
event: 'onload',
|
||||
fn: () => window.userGuiding.identify(userId.toString())
|
||||
});
|
||||
|
||||
window.userGuidingLayer.push({
|
||||
event: 'onIdentificationComplete',
|
||||
fn: callback
|
||||
});
|
||||
};
|
||||
|
||||
const attemptDisplayUserGuidingSurvey = (userId, permissions, guideId, callback) => {
|
||||
if (!USER_GUIDING_ID || !process.env.SORTING_HAT_HOST) {
|
||||
return;
|
||||
}
|
||||
|
||||
api({
|
||||
uri: '/user_guiding',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-USERID': userId,
|
||||
'X-PERMISSIONS': JSON.stringify(permissions),
|
||||
'X-CONDITIONS': JSON.stringify(CONDITIONS),
|
||||
'X-QUESTION-NUMBER': guideId
|
||||
},
|
||||
host: process.env.SORTING_HAT_HOST
|
||||
}, (err, body, res) => {
|
||||
if (err || res.statusCode !== 200) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (body?.result === 'true') {
|
||||
activateUserGuiding(userId, callback);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onCommented = (userId, permissions) => {
|
||||
attemptDisplayUserGuidingSurvey(
|
||||
userId,
|
||||
permissions,
|
||||
AUTONOMY_SURVEY_ID,
|
||||
() => window.userGuiding.launchSurvey(AUTONOMY_SURVEY_ID)
|
||||
);
|
||||
};
|
||||
|
||||
const onProjectShared = (userId, permissions) => {
|
||||
attemptDisplayUserGuidingSurvey(
|
||||
userId,
|
||||
permissions,
|
||||
RELATIONSHIP_SURVEY_ID,
|
||||
() => window.userGuiding.launchSurvey(RELATIONSHIP_SURVEY_ID)
|
||||
);
|
||||
};
|
||||
|
||||
const onProjectLoaded = (userId, permissions) => {
|
||||
const surveyId = sample(EDITOR_INTERACTION_SURVEY_IDS);
|
||||
|
||||
attemptDisplayUserGuidingSurvey(
|
||||
userId,
|
||||
permissions,
|
||||
surveyId,
|
||||
() => window.userGuiding.launchSurvey(surveyId)
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
onProjectLoaded,
|
||||
onCommented,
|
||||
onProjectShared
|
||||
};
|
|
@ -34,13 +34,15 @@ const thumbnailUrl = require('../../lib/user-thumbnail');
|
|||
const FormsyProjectUpdater = require('./formsy-project-updater.jsx');
|
||||
const EmailConfirmationModal = require('../../components/modal/email-confirmation/modal.jsx');
|
||||
const EmailConfirmationBanner = require('../../components/dropdown-banner/email-confirmation/banner.jsx');
|
||||
const {onCommented} = require('../../lib/user-guiding.js');
|
||||
const queryString = require('query-string').default;
|
||||
|
||||
const projectShape = require('./projectshape.jsx').projectShape;
|
||||
require('./preview.scss');
|
||||
|
||||
const frameless = require('../../lib/frameless');
|
||||
const {useState, useCallback} = require('react');
|
||||
const {useState, useEffect} = require('react');
|
||||
const ProjectJourney = require('../../components/journeys/project-journey/project-journey.jsx');
|
||||
const {triggerAnalyticsEvent, shouldDisplayOnboarding} = require('../../lib/onboarding.js');
|
||||
|
||||
// disable enter key submission on formsy input fields; otherwise formsy thinks
|
||||
// we meant to trigger the "See inside" button. Instead, treat these keypresses
|
||||
|
@ -148,7 +150,15 @@ const PreviewPresentation = ({
|
|||
userUsesParentEmail,
|
||||
visibilityInfo
|
||||
}) => {
|
||||
const [hasSubmittedComment, setHasSubmittedComment] = useState(false);
|
||||
const [canViewProjectJourney, setCanViewProjectJourney] = useState(false);
|
||||
const [shouldStopProject, setShouldStopProject] = useState(false);
|
||||
useEffect(() => {
|
||||
setCanViewProjectJourney(
|
||||
queryString.parse(location.search, {parseBooleans: true}).showJourney &&
|
||||
!userOwnsProject &&
|
||||
shouldDisplayOnboarding(user, permissions)
|
||||
);
|
||||
}, [userOwnsProject, user, permissions]);
|
||||
const shareDate = ((projectInfo.history && projectInfo.history.shared)) ? projectInfo.history.shared : '';
|
||||
const revisedDate = ((projectInfo.history && projectInfo.history.modified)) ? projectInfo.history.modified : '';
|
||||
const showInstructions = editable || projectInfo.instructions ||
|
||||
|
@ -221,15 +231,16 @@ const PreviewPresentation = ({
|
|||
))}
|
||||
</FlexRow>
|
||||
);
|
||||
|
||||
const onAddCommentWrapper = useCallback(body => {
|
||||
onAddComment(body);
|
||||
if (!hasSubmittedComment && user) {
|
||||
setHasSubmittedComment(true);
|
||||
onCommented(user.id, permissions);
|
||||
}
|
||||
}, [hasSubmittedComment, user]);
|
||||
|
||||
useEffect(() => {
|
||||
if (canViewProjectJourney && projectInfo.title) {
|
||||
triggerAnalyticsEvent({
|
||||
event: 'editor-journey-step',
|
||||
editorJourneyStep: `${projectInfo.title}-Starter-Project`
|
||||
});
|
||||
}
|
||||
}, [canViewProjectJourney, projectInfo.title]);
|
||||
|
||||
return (
|
||||
<div className="preview">
|
||||
{showEmailConfirmationModal && <EmailConfirmationModal
|
||||
|
@ -256,6 +267,14 @@ const PreviewPresentation = ({
|
|||
)}
|
||||
{ projectInfo && projectInfo.author && projectInfo.author.id && (
|
||||
<React.Fragment>
|
||||
{
|
||||
isProjectLoaded &&
|
||||
canViewProjectJourney &&
|
||||
<ProjectJourney
|
||||
setCanViewProjectJourney={setCanViewProjectJourney}
|
||||
setShouldStopProject={setShouldStopProject}
|
||||
/>
|
||||
}
|
||||
{showEmailConfirmationBanner && <EmailConfirmationBanner
|
||||
userUsesParentEmail={userUsesParentEmail}
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
|
@ -392,6 +411,7 @@ const PreviewPresentation = ({
|
|||
onUpdateProjectData={onUpdateProjectData}
|
||||
onUpdateProjectId={onUpdateProjectId}
|
||||
onUpdateProjectThumbnail={onUpdateProjectThumbnail}
|
||||
shouldStopProject={shouldStopProject}
|
||||
/>
|
||||
</div>
|
||||
<MediaQuery maxWidth={frameless.tabletPortrait - 1}>
|
||||
|
@ -626,7 +646,7 @@ const PreviewPresentation = ({
|
|||
isLoggedIn ? (
|
||||
isShared && <ComposeComment
|
||||
postURI={`/proxy/comments/project/${projectId}`}
|
||||
onAddComment={onAddCommentWrapper}
|
||||
onAddComment={onAddComment}
|
||||
/>
|
||||
) : (
|
||||
/* TODO add box for signing in to leave a comment */
|
||||
|
|
|
@ -8,7 +8,7 @@ const PropTypes = require('prop-types');
|
|||
const connect = require('react-redux').connect;
|
||||
const injectIntl = require('react-intl').injectIntl;
|
||||
const parser = require('scratch-parser');
|
||||
const queryString = require('query-string');
|
||||
const queryString = require('query-string').default;
|
||||
|
||||
const api = require('../../lib/api');
|
||||
const Page = require('../../components/page/www/page.jsx');
|
||||
|
@ -25,10 +25,6 @@ const ConnectedLogin = require('../../components/login/connected-login.jsx');
|
|||
const CanceledDeletionModal = require('../../components/login/canceled-deletion-modal.jsx');
|
||||
const NotAvailable = require('../../components/not-available/not-available.jsx');
|
||||
const Meta = require('./meta.jsx');
|
||||
const {
|
||||
onProjectShared,
|
||||
onProjectLoaded
|
||||
} = require('../../lib/user-guiding.js');
|
||||
|
||||
const sessionActions = require('../../redux/session.js');
|
||||
const {selectProjectCommentsGloballyEnabled, selectIsTotallyNormal} = require('../../redux/session');
|
||||
|
@ -44,16 +40,48 @@ const IntlGUI = injectIntl(GUI.default);
|
|||
const localStorageAvailable = 'localStorage' in window && window.localStorage !== null;
|
||||
|
||||
const xhr = require('xhr');
|
||||
const {useEffect} = require('react');
|
||||
const {useEffect, useState} = require('react');
|
||||
const EditorJourney = require('../../components/journeys/editor-journey/editor-journey.jsx');
|
||||
const {usePrevious} = require('react-use');
|
||||
const TutorialsHighlight = require('../../components/journeys/tutorials-highlight/tutorials-highlight.jsx');
|
||||
const {triggerAnalyticsEvent, sendUserProperties, shouldDisplayOnboarding} = require('../../lib/onboarding.js');
|
||||
|
||||
const IntlGUIWithProjectHandler = ({...props}) => {
|
||||
const [showJourney, setShowJourney] = useState(false);
|
||||
const [canViewTutorialsHighlight, setCanViewTutorialsHighlight] = useState(false);
|
||||
const prevProjectId = usePrevious(props.projectId);
|
||||
|
||||
const IntlGUIWithProjectHandler = ({user, permissions, ...props}) => {
|
||||
useEffect(() => {
|
||||
if (props.projectId && props.projectId !== '0') {
|
||||
onProjectLoaded(user.id, permissions);
|
||||
}
|
||||
}, [props.projectId, user.id, permissions]);
|
||||
const isTutorialOpen = !!queryString.parse(location.search).tutorial;
|
||||
|
||||
return <IntlGUI {...props} />;
|
||||
if (
|
||||
(prevProjectId === 0 || prevProjectId === '0') &&
|
||||
props.projectId &&
|
||||
props.projectId !== '0' &&
|
||||
!isTutorialOpen &&
|
||||
shouldDisplayOnboarding(props.user, props.permissions)
|
||||
) {
|
||||
setShowJourney(true);
|
||||
}
|
||||
}, [props.projectId, prevProjectId, props.user, props.permissions]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<IntlGUI {...props} />
|
||||
{showJourney && (
|
||||
<EditorJourney
|
||||
onActivateDeck={props.onActivateDeck}
|
||||
setCanViewTutorialsHighlight={setCanViewTutorialsHighlight}
|
||||
setShowJourney={setShowJourney}
|
||||
/>
|
||||
)}
|
||||
{canViewTutorialsHighlight && (
|
||||
<TutorialsHighlight
|
||||
setCanViewTutorialsHighlight={setCanViewTutorialsHighlight}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
IntlGUIWithProjectHandler.propTypes = {
|
||||
|
@ -226,6 +254,10 @@ class Preview extends React.Component {
|
|||
false // Do not show cloud/username alerts again
|
||||
);
|
||||
}
|
||||
|
||||
if (!prevProps.user.id && this.props.user.id && this.props.permissions) {
|
||||
sendUserProperties(this.props.user, this.props.permissions);
|
||||
}
|
||||
}
|
||||
componentWillUnmount () {
|
||||
this.removeEventListeners();
|
||||
|
@ -511,6 +543,15 @@ class Preview extends React.Component {
|
|||
if (!this.state.greenFlagRecorded) {
|
||||
this.props.logProjectView(this.props.projectInfo.id, this.props.authorUsername, this.props.user.token);
|
||||
}
|
||||
|
||||
const showJourney = queryString.parse(location.search, {parseBooleans: true}).showJourney;
|
||||
if (showJourney && shouldDisplayOnboarding(this.props.user, this.props.permissions)) {
|
||||
triggerAnalyticsEvent({
|
||||
event: 'tutorial-played',
|
||||
playedProject: this.props.projectInfo.title
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
showUsernameBlockAlert: false,
|
||||
showCloudDataAlert: false,
|
||||
|
@ -622,6 +663,14 @@ class Preview extends React.Component {
|
|||
}
|
||||
}
|
||||
handleRemix () {
|
||||
const showJourney = queryString.parse(location.search, {parseBooleans: true}).showJourney;
|
||||
if (showJourney && shouldDisplayOnboarding(this.props.user, this.props.permissions)) {
|
||||
triggerAnalyticsEvent({
|
||||
event: 'tutorial-remixed',
|
||||
remixedProject: this.props.projectInfo.title
|
||||
});
|
||||
}
|
||||
|
||||
// Update the state first before starting the remix to show spinner
|
||||
this.setState({isRemixing: true}, () => {
|
||||
this.props.remixProject();
|
||||
|
@ -650,7 +699,6 @@ class Preview extends React.Component {
|
|||
justRemixed: false,
|
||||
justShared: true
|
||||
});
|
||||
onProjectShared(this.props.user.id, this.props.permissions);
|
||||
}
|
||||
handleShareAttempt () {
|
||||
this.setState({
|
||||
|
@ -691,6 +739,7 @@ class Preview extends React.Component {
|
|||
const parts = window.location.pathname.toLowerCase()
|
||||
.split('/')
|
||||
.filter(Boolean);
|
||||
const queryParams = location.search;
|
||||
let newUrl;
|
||||
if (projectId === '0') {
|
||||
newUrl = `/${parts[0]}/editor`;
|
||||
|
@ -702,7 +751,7 @@ class Preview extends React.Component {
|
|||
history.pushState(
|
||||
{projectId: projectId},
|
||||
{projectId: projectId},
|
||||
newUrl
|
||||
`${newUrl}${queryParams}`
|
||||
);
|
||||
if (callback) callback();
|
||||
});
|
||||
|
@ -908,6 +957,7 @@ class Preview extends React.Component {
|
|||
onUpdateProjectTitle={this.handleUpdateProjectTitle}
|
||||
user={this.props.user}
|
||||
permissions={this.props.permissions}
|
||||
onActivateDeck={this.props.onActivateDeck}
|
||||
/>
|
||||
)}
|
||||
{this.props.registrationOpen && (
|
||||
|
@ -985,6 +1035,7 @@ Preview.propTypes = {
|
|||
lovedLoaded: PropTypes.bool,
|
||||
moreCommentsToLoad: PropTypes.bool,
|
||||
original: projectShape,
|
||||
onActivateDeck: PropTypes.func,
|
||||
parent: projectShape,
|
||||
permissions: PropTypes.object,
|
||||
playerMode: PropTypes.bool,
|
||||
|
@ -1241,6 +1292,9 @@ const mapDispatchToProps = dispatch => ({
|
|||
},
|
||||
setFullScreen: fullscreen => {
|
||||
dispatch(GUI.setFullScreen(fullscreen));
|
||||
},
|
||||
onActivateDeck: id => {
|
||||
dispatch(GUI.activateDeck(id));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
1
static/images/onboarding-journeys/Animation-Icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 287.25 289.31"><g id="Layer_1-2"><g><polygon points="5.62 66.87 17.11 111.29 263.82 47.87 254.29 3.94 5.62 66.87" fill="#fbaa1e"/><circle cx="152.92" cy="203.26" r="62.8" fill="#fbaa1e" stroke="#1e1e1e" stroke-miterlimit="10" stroke-width="8.09"/><g><path d="M283.6,112.42H40.71l228.64-58.13c1.95-.53,3.09-2.54,2.56-4.48l-10.48-38.46c-2.23-8.2-10.72-13.05-18.91-10.81L11.36,59.36C3.16,61.59-1.69,70.08,.54,78.27l10.05,36.91c-.07,.29-.12,.58-.12,.9v157.83c0,8.49,6.91,15.41,15.41,15.41h245.97c8.49,0,15.41-6.91,15.41-15.41V116.07c0-2.02-1.63-3.65-3.65-3.65ZM108.92,42.22l42.38,34.38-44.76,10.23-42.38-34.38,44.76-10.23Zm48.04-12.6l46.88,34.38-42.06,10.23-42.38-34.38,37.57-10.23Zm56.13-13.5l42.38,34.38-42.96,11.13-42.38-34.38,42.96-11.13Zm31.34-8.54c4.31-1.18,8.78,1.38,9.95,5.69l9.42,34.59L221.77,13.76l22.67-6.18ZM55.49,54.82l42.38,34.38-43.86,12.03L11.9,66.92c.44-.21,.9-.39,1.38-.52l42.21-11.58ZM7.45,72.72l37.9,30.88-28.24,7.7L7.59,76.35c-.33-1.21-.37-2.44-.14-3.64Zm272.5,201.19c0,4.47-3.64,8.1-8.11,8.1H25.88c-4.47,0-8.1-3.64-8.1-8.1V119.72H279.95v154.18Z" fill="#1e1e1e"/><path d="M125.19,240.95c1.16,.67,2.42,1,3.69,1s2.54-.33,3.69-1l56.09-32.38c2.31-1.34,3.69-3.73,3.69-6.4s-1.38-5.06-3.69-6.4l-56.09-32.38c-2.31-1.34-5.07-1.34-7.39,0-2.31,1.33-3.69,3.73-3.69,6.4v64.77c0,2.67,1.38,5.06,3.69,6.4Zm3.61-71.21s.04-.04,.09-.04c.01,0,.02,0,.04,0l56.09,32.38,1.83-3.16-1.79,3.18s.01,.08-.03,.13l-56.13,32.4s-.08-.03-.09-.09v-64.81Z" fill="#1e1e1e"/></g><path d="M128.8,169.74s.04-.04,.09-.04c.01,0,.02,0,.04,0l56.09,32.38,1.83-3.16-1.79,3.18s.01,.08-.03,.13l-56.13,32.4s-.08-.03-.09-.09v-64.81Z" fill="#7860aa"/></g></g></svg>
|
After Width: | Height: | Size: 1.7 KiB |
BIN
static/images/onboarding-journeys/Character-Animation.jpg
Normal file
After Width: | Height: | Size: 109 KiB |
BIN
static/images/onboarding-journeys/Clicker-Game.jpg
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
static/images/onboarding-journeys/Fly-Animation.jpg
Normal file
After Width: | Height: | Size: 39 KiB |
1
static/images/onboarding-journeys/Games-Icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 257.29 252.8"><g id="Layer_1-2"><g><circle cx="130.2" cy="124.6" r="120.27" fill="#fbaa1e"/><circle cx="132.35" cy="122.46" r="95.72" fill="#fff"/><circle cx="131.41" cy="125.24" r="30.02" fill="#7e69af"/><polygon points="13.77 75.45 38.17 94.28 60.83 82.42 32.94 65.34 13.77 75.45" fill="#7e69af"/><polygon points="35.58 37.94 63.22 51.56 62.56 77.13 34.94 59.61 35.58 37.94" fill="#7e69af"/><g><path d="M104.12,140.6l-1.56-4.03c-.21-.54-.4-1.08-.58-1.64l-1.33-4.11,8.23-2.66,1.33,4.11c.13,.4,.27,.79,.42,1.17l1.56,4.03-8.06,3.13Z" fill="#1e1e1e"/><path d="M131.7,156.54c-7.21,0-14.25-2.52-19.81-7.08l-3.34-2.74,5.49-6.68,3.34,2.74c4.02,3.3,9.11,5.12,14.33,5.12,12.46,0,22.6-10.14,22.6-22.6s-10.14-22.6-22.6-22.6c-1.1,0-2.21,.08-3.3,.24l-4.28,.62-1.24-8.56,4.28-.62c1.5-.22,3.02-.33,4.54-.33,17.23,0,31.25,14.02,31.25,31.25s-14.02,31.25-31.25,31.25Z" fill="#1e1e1e"/><path d="M75.74,120.35l-8.51-1.51,.75-4.26c.3-1.7,.68-3.41,1.11-5.07l1.1-4.18,8.36,2.19-1.1,4.18c-.38,1.44-.7,2.92-.96,4.39l-.75,4.26Z" fill="#1e1e1e"/><path d="M131.05,189.86c-15.49,0-30.44-5.6-42.1-15.78-11.54-10.08-19.1-23.94-21.29-39.02l-.62-4.28,8.56-1.24,.62,4.28c3.91,27.02,27.48,47.4,54.83,47.4,30.55,0,55.4-24.85,55.4-55.4s-24.85-55.4-55.4-55.4c-4.97,0-9.9,.66-14.65,1.96l-4.17,1.14-2.28-8.34,4.17-1.14c5.49-1.5,11.19-2.26,16.93-2.26,35.32,0,64.05,28.73,64.05,64.05s-28.73,64.05-64.05,64.05Z" fill="#1e1e1e"/><path d="M94.55,83.91l-5.16-6.94,3.47-2.58c1.46-1.08,2.98-2.11,4.52-3.07l3.68-2.27,4.55,7.35-3.68,2.27c-1.33,.82-2.65,1.72-3.91,2.65l-3.47,2.58Z" fill="#1e1e1e"/><path d="M40.15,123.75l-8.63-.57,.28-4.31c.1-1.54,.24-3.11,.42-4.64l.49-4.3,8.59,.97-.49,4.3c-.16,1.4-.29,2.83-.38,4.24l-.28,4.31Z" fill="#1e1e1e"/><path d="M130.89,224.83c-24.68,0-48.34-9.12-66.62-25.67-18.15-16.43-29.56-38.83-32.12-63.08l-.45-4.3,8.6-.91,.45,4.3c4.88,46.18,43.64,81.01,90.15,81.01,49.99,0,90.66-40.67,90.66-90.65s-40.67-90.66-90.66-90.66c-11.07,0-21.89,1.97-32.15,5.86l-4.04,1.53-3.07-8.08,4.04-1.53c11.24-4.26,23.09-6.43,35.21-6.43,54.76,0,99.3,44.55,99.3,99.3s-44.55,99.3-99.3,99.3Z" fill="#1e1e1e"/><path d="M79.04,51.02l-4.6-7.32,3.66-2.3c.94-.59,1.9-1.17,2.86-1.73l3.73-2.18,4.35,7.47-3.73,2.18c-.88,.51-1.75,1.04-2.61,1.58l-3.66,2.3Z" fill="#1e1e1e"/><rect x="69.62" y="14.07" width="8.65" height="147.15" transform="translate(-39.88 101.61) rotate(-56.84)" fill="#1e1e1e"/><polygon points="62.89 87.44 58.54 53.99 40.37 41.47 39.34 67.5 30.7 67.16 32.35 25.45 66.62 49.05 71.47 86.32 62.89 87.44" fill="#1e1e1e"/><polygon points="36.32 99.83 0 73.51 37.2 55.49 40.97 63.27 16.78 74.99 37.56 90.05 66.76 78.55 69.93 86.59 36.32 99.83" fill="#1e1e1e"/><path d="M14.47,108.08l-8.49-1.65,.82-4.24c.29-1.52,.62-3.05,.97-4.56l.98-4.21,8.42,1.96-.98,4.21c-.33,1.41-.63,2.84-.91,4.25l-.82,4.24Z" fill="#1e1e1e"/><path d="M130.89,252.8c-69.7,0-126.4-56.7-126.4-126.4,0-1.98,.05-3.98,.14-5.95l.2-4.32,8.64,.4-.2,4.32c-.09,1.84-.13,3.7-.13,5.55,0,64.93,52.82,117.75,117.75,117.75s117.75-52.82,117.75-117.75S195.82,8.65,130.89,8.65c-17.33,0-34.01,3.67-49.6,10.92l-3.92,1.82-3.65-7.84,3.92-1.82C94.38,3.95,112.29,0,130.89,0c69.7,0,126.4,56.7,126.4,126.4s-56.7,126.4-126.4,126.4Z" fill="#1e1e1e"/><path d="M59.39,32.73l-4.99-7.06,3.53-2.5c1.16-.82,2.34-1.63,3.53-2.41l3.61-2.38,4.76,7.22-3.61,2.38c-1.11,.73-2.22,1.49-3.29,2.25l-3.53,2.5Z" fill="#1e1e1e"/></g></g></g></svg>
|
After Width: | Height: | Size: 3.4 KiB |
BIN
static/images/onboarding-journeys/Make-Music.jpg
Normal file
After Width: | Height: | Size: 124 KiB |
1
static/images/onboarding-journeys/Music-Icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 328.03 253.62"><g id="Layer_1-2"><g><polygon points="84.02 101.63 76.8 115.85 109.59 139.6 119.32 126.19 84.02 101.63" fill="#f68c24"/><polygon points="75.64 121.2 2.61 236.4 21.81 251.09 105.73 138.5 76.8 115.85 75.64 121.2" fill="#7e69af" fill-rule="evenodd"/><g><path d="M299.39,50.51c.82-5.54,5.98-9.36,11.51-8.54l-15.08,101.51c-.15,10.79-13.15,20.55-30.01,22.18-17.64,1.71-32.84-6.12-33.94-17.49-1.1-11.37,12.31-21.97,29.95-23.68,10.14-.98,19.47,1.19,25.78,5.37l11.79-79.35Zm11.52-8.53c10.39,2.61,26.86,18.97,9.64,63.48-1.08,2.47-2.8,5.96-4.9,9.55-2.77,4.72-6.29,2-4.53-3.18,.86-2.54,1.6-5.08,2.13-7.45,2.52-11.39,7.88-36.27-4.83-45.65l2.49-16.75Z" fill="#231f20" fill-rule="evenodd"/><path d="M208.72,10.36c.64-2.98,3.39-4.96,6.35-4.71v-.05s.77,.16,.77,.16l45.66,9.84,5.26,1.13h0s.05,.01,.08,.02l-12.78,59.31c-.5,6.36-8.54,11.62-18.55,11.94-10.47,.33-19.14-4.87-19.35-11.62-.22-6.75,8.1-12.49,18.58-12.83,6.02-.19,11.44,1.44,15,4.15l7.94-36.86-45.14-9.72-9.47,43.95c-.5,6.36-8.54,11.62-18.55,11.94-10.47,.33-19.14-4.87-19.35-11.62-.22-6.75,8.1-12.49,18.58-12.83,6.02-.19,11.44,1.44,15,4.15l9.99-46.36Z" fill="#231f20" fill-rule="evenodd"/></g><path d="M152.13,63.42l-.64-.65c-8.53-8.02-17.19-13.27-25.99-15.74-2.6-.79-5.68-.99-9.22-.6-2.48,.13-5.88,1.56-10.19,4.31-3.88,2.12-9.01,7.3-15.39,15.54-5.26,7.09-8.64,13.12-10.13,18.1-1.33,4.93-1.54,8.61-.63,11.05,.5,1.57,1.14,3.07,1.9,4.49-2.62,3.48-4.42,6.45-5.36,8.89-.71,2.14-.61,4.49,.31,7.05,.08,.18,.18,.36,.28,.55-22.03,34.63-43.68,68.65-56.45,88.35-3.65,5.63-6.6,10.13-8.94,13.7-1.57,2.4-2.88,4.39-3.98,6.08-3.19,4.92-4.67,7.38-5.32,9.37-.96,2.96-.09,4.9,1.58,6.62,.82,.85,1.83,1.64,2.9,2.48,.35,.28,.73,.57,1.12,.87,3.3,2.53,7.93,5.91,10.9,7.17,4.43,1.88,6.26-.96,10.74-6.82l.33-.44c5.15-6.74,14.56-18.92,23.97-31.12h.01s55.17-73.13,55.17-73.13h0s.01-.02,.01-.02c.15,.03,.3,.06,.45,.08,1.6,.27,3.07-.03,4.41-.9,1.5-.77,3.29-2.36,5.38-4.75,.73-.95,2.11-3,4.12-6.13,4.39,.45,8.24-.47,11.57-2.74,.73-.28,2.45-1.8,5.16-4.54,3.18-3.75,5.78-7.28,7.81-10.59,5.45-8.01,9.27-15.56,11.46-22.65,2.34-8.79-.12-16.76-7.38-23.88Zm-43,76.11h0s0,0,0,0h0Zm-57.83,71.11c-9.41,12.19-18.82,24.37-23.97,31.12l-.33,.44c-5.24,6.85-5.25,6.43-6.79,5.77-2.5-1.06-6.79-4.15-10.17-6.74-.38-.29-.75-.58-1.1-.85-1.18-.92-2.02-1.62-2.59-2.21-1.13-1.17-1.2-1.92-.77-3.24,.48-1.48,1.67-3.53,4.94-8.58,1.09-1.69,2.39-3.67,3.97-6.07,2.34-3.57,5.29-8.07,8.95-13.71,12.65-19.51,33.98-53.04,55.77-87.29,1.02,1.1,2.29,2.27,3.85,3.51,.58,.48,1.19,.96,1.83,1.43l17.18,12.35c1.21,.8,2.43,1.45,3.66,1.95l-54.43,72.14Zm61.86-74.98c-.44,.26-.95,.43-1.51,.53l.06-.09h0c-.56,.16-1.14,.2-1.77,.15-.04,0-.07,0-.11,0-.29-.03-.58-.09-.88-.16-.12-.03-.24-.07-.36-.1-.18-.05-.36-.11-.55-.17-1.24-.44-2.67-1.15-4.3-2.12l-15.99-11.51-3.48-2.73c-1.89-1.53-3.33-3.17-4.34-4.9-.29-.58-.48-1.17-.57-1.76h0s0,0,0,0c-.03-.25-.04-.51-.03-.78,.03-.83,.23-1.75,.6-2.75l.68-1.57c.72-1.45,1.76-3.06,3.09-4.82,1.05,1.46,2.25,2.82,3.61,4.1,7.63,7.58,16.93,13.79,27.91,18.63,1.64,.67,3.22,1.19,4.74,1.56-3.26,4.88-5.53,7.71-6.8,8.47Z" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/><path d="M152.75,150.65c-3.52-.05-6.79-2.27-8.02-5.79-1.56-4.49,.81-9.4,5.31-10.97,9.77-3.4,16.89-11.91,18.58-22.21,.77-4.7,5.19-7.88,9.89-7.12,4.7,.77,7.88,5.2,7.11,9.89-2.7,16.54-14.16,30.23-29.92,35.71-.98,.34-1.97,.49-2.95,.48Z" fill="#fbaa1e"/><path d="M166.39,181.86c-3.49-.05-6.74-2.23-7.99-5.71-1.61-4.48,.72-9.41,5.19-11.02,20.13-7.23,34.67-24.76,37.93-45.74,.73-4.7,5.11-7.92,9.84-7.19,4.7,.73,7.92,5.14,7.19,9.84-4.23,27.22-23.06,49.95-49.13,59.31-1,.36-2.03,.52-3.03,.51Z" fill="#fbaa1e"/><path d="M70.5,31.21c3.52,.05,6.79,2.27,8.02,5.79,1.56,4.49-.81,9.4-5.31,10.97-9.77,3.4-16.89,11.91-18.58,22.21-.77,4.7-5.19,7.88-9.89,7.12-4.7-.77-7.88-5.2-7.11-9.89,2.7-16.54,14.16-30.23,29.92-35.71,.98-.34,1.97-.49,2.95-.48Z" fill="#fbaa1e"/><path d="M56.87,0c3.49,.05,6.74,2.23,7.99,5.71,1.61,4.48-.72,9.41-5.19,11.02-20.13,7.23-34.67,24.76-37.93,45.74-.73,4.7-5.11,7.92-9.84,7.19-4.7-.73-7.92-5.14-7.19-9.84C8.93,32.6,27.76,9.87,53.83,.51,54.84,.15,55.86-.01,56.87,0Z" fill="#fbaa1e"/></g></g></svg>
|
After Width: | Height: | Size: 4.1 KiB |
BIN
static/images/onboarding-journeys/Name-Art.jpg
Normal file
After Width: | Height: | Size: 134 KiB |
1
static/images/onboarding-journeys/On-Own-Icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 331.84 300.12"><g id="Layer_1-2"><g><path d="M183.85,193.08l-41.73,72.19c-2.09,3.61-8.19,4.02-13.64,.87-5.45-3.15-8.14-8.64-6.05-12.25l41.73-72.19,19.69,11.38Z" fill="#7e69af"/><g><g><path d="M162.09,138.64l4.81-8.32c3.3-5.71,4.88-12.39,3.77-18.9-1.66-9.7,.56-20.14,7.33-28.83,10.09-12.95,28.17-17.51,43.23-10.96,20.44,8.9,28.05,33.23,17.22,51.96-4.45,7.7-11.27,13.17-19.01,16.02-6.12,2.26-11.12,6.83-14.38,12.49l-4.93,8.54" fill="#febf10" stroke="#ffd996" stroke-linecap="round" stroke-linejoin="round" stroke-width="7.32"/><path d="M182.72,190.73l-41.7,72.13c-.9,1.55-2.06,2.91-3.48,4.04l-32.5,27.98c-5.12,4.1-12.85-.37-11.85-6.85l8.03-42.12c.27-1.79,.87-3.47,1.76-5.03l33.46-57.87,8.25-14.26" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="7.32"/><path d="M184.63,172.81l1.58,.92c4.38,2.53,5.87,8.13,3.34,12.5h0c-2.53,4.38-8.13,5.87-12.5,3.34l-28.52-16.49c-4.38-2.53-5.87-8.13-3.34-12.5h0c2.53-4.38,8.13-5.87,12.5-3.34l1.58,.92" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="7.32"/><path d="M159.27,158.15l-4.75-2.75c-4.38-2.53-5.87-8.13-3.34-12.5h0c2.53-4.38,8.13-5.87,12.5-3.34l34.86,20.16c4.38,2.53,5.87,8.13,3.34,12.5h0c-2.53,4.38-8.13,5.87-12.5,3.34l-4.75-2.75" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="7.32"/><polygon points="119.01 282.87 107.82 276.4 96.63 269.93 97.37 295.78 119.01 282.87" fill="#231f20" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="7.32"/><path d="M162.09,138.64l4.81-8.32c3.3-5.71,4.88-12.39,3.77-18.9-1.66-9.7,.56-20.14,7.33-28.83,10.09-12.95,28.17-17.51,43.23-10.96,20.44,8.9,28.05,33.23,17.22,51.96-4.45,7.7-11.27,13.17-19.01,16.02-6.12,2.26-11.12,6.83-14.38,12.49l-4.93,8.54" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="7.32"/><polyline points="191.76 109.27 193.94 127.45 210.77 120.27" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="7.32"/><line x1="193.94" y1="127.45" x2="188.44" y2="136.95" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="7.32"/><line x1="232.41" y1="60.89" x2="236.08" y2="54.56" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="7.32"/><line x1="149.22" y1="80.45" x2="155.56" y2="84.12" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="7.32"/><line x1="250.64" y1="139.09" x2="256.97" y2="142.75" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="7.32"/><line x1="260.79" y1="100.56" x2="268.11" y2="100.56" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="7.32"/><line x1="180.22" y1="49.75" x2="183.88" y2="56.09" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="7.32"/><path d="M162.79,181.32l-40.31,69.72c-2.02,3.49-7.91,3.88-13.17,.84-5.26-3.04-7.86-8.35-5.84-11.84l40.31-69.72,19.02,10.99Z" fill="#7e69af" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="7.32"/><path d="M181.8,192.32l-40.31,69.72c-2.02,3.49-7.91,3.88-13.17,.84-5.26-3.04-7.86-8.35-5.84-11.84l40.31-69.72,19.02,10.99Z" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="7.32"/></g><g><path d="M312.92,249.61c-1.78,0-3.23-1.45-3.23-3.23v-62.08c0-1.78,1.45-3.23,3.23-3.23s3.23,1.45,3.23,3.23v62.08c0,1.78-1.45,3.23-3.23,3.23Z" fill="#1e1e1e"/><path d="M328.61,265.3H179.95c-1.78,0-3.23-1.45-3.23-3.23s1.45-3.23,3.23-3.23h145.43V39.45H46.3c-1.78,0-3.23-1.45-3.23-3.23s1.45-3.23,3.23-3.23H328.61c1.78,0,3.23,1.45,3.23,3.23v225.85c0,1.78-1.45,3.23-3.23,3.23Z" fill="#1e1e1e"/><path d="M65.4,265.3H33.14c-18.28,0-33.14-14.87-33.14-33.14,0-14.98,12.18-27.16,27.16-27.16,12.34,0,22.37,10.04,22.37,22.37,0,10.23-8.32,18.54-18.54,18.54-8.54,0-15.48-6.94-15.48-15.48,0-1.78,1.45-3.23,3.23-3.23s3.23,1.45,3.23,3.23c0,4.98,4.04,9.02,9.02,9.02,6.66,0,12.08-5.42,12.08-12.08,0-8.78-7.14-15.91-15.91-15.91-11.41,0-20.7,9.29-20.7,20.7,0,14.71,11.97,26.68,26.68,26.68h32.26c1.78,0,3.23,1.45,3.23,3.23s-1.45,3.23-3.23,3.23Z" fill="#1e1e1e"/><path d="M3.23,235.38c-1.78,0-3.23-1.45-3.23-3.23V27.16C0,12.18,12.18,0,27.16,0c12.34,0,22.37,10.04,22.37,22.37V227.37c0,1.78-1.45,3.23-3.23,3.23s-3.23-1.45-3.23-3.23V22.37c0-8.78-7.14-15.91-15.91-15.91C15.74,6.46,6.46,15.75,6.46,27.16V232.15c0,1.78-1.45,3.23-3.23,3.23Z" fill="#1e1e1e"/><g><path d="M18.92,107.72c-1.78,0-3.23-1.45-3.23-3.23V33.22c0-1.78,1.45-3.23,3.23-3.23s3.23,1.45,3.23,3.23V104.49c0,1.78-1.45,3.23-3.23,3.23Z" fill="#1e1e1e"/><path d="M18.92,129.78c-1.78,0-3.23-1.45-3.23-3.23v-7.34c0-1.78,1.45-3.23,3.23-3.23s3.23,1.45,3.23,3.23v7.34c0,1.78-1.45,3.23-3.23,3.23Z" fill="#1e1e1e"/></g></g></g></g></g></svg>
|
After Width: | Height: | Size: 4.8 KiB |
BIN
static/images/onboarding-journeys/Pong-Game.jpg
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
static/images/onboarding-journeys/Record-Music.jpg
Normal file
After Width: | Height: | Size: 132 KiB |
After Width: | Height: | Size: 6.3 KiB |
1
static/images/onboarding-journeys/Tutorials-Icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 349.79 255.18"><g id="Layer_1-2"><g><path d="M94.31,91.76s13.65-16.35,49.71-15.95c36.06,.41,49.29,17.02,49.33,17.06l67.63,.76c2.28,.03,4.11,1.9,4.08,4.17l-.56,49.38c-.03,2.28-1.89,4.11-4.17,4.08l-113.03-1.28h0s-2.42-.03-2.42-.03c-1.09-.01-2.15,.41-2.93,1.18l-5.9,5.77c-.78,.77-1.84,1.19-2.93,1.18l-13.09-.15c-1.09-.01-2.14-.46-2.9-1.24l-5.77-5.9c-.76-.78-1.81-1.23-2.9-1.24l-2.42-.03h0l-8.25-.09c-2.28-.03-4.1-1.89-4.08-4.17l.6-53.5h0Z" fill="#f9a11b" fill-rule="evenodd" stroke="#000" stroke-miterlimit="10" stroke-width="3.79"/><path d="M147.74,149.98h1.97s0-.01,0-.01l80.1-.51c2.36-.02,4.28,1.88,4.29,4.24l.19,43.66c0,2.36-1.89,4.28-4.25,4.3l-80.1,.51h0s-1.97,.01-1.97,.01c-1.54,0-3.02,.63-4.1,1.73l-5.32,5.38c-1.08,1.1-2.56,1.72-4.1,1.73l-12.68,.08c-1.54,.01-3.02-.59-4.11-1.67l-5.37-5.31c-1.09-1.08-2.57-1.68-4.11-1.67h-1.97s0,.01,0,.01l-8.85,.06c-2.36,.01-4.28-1.89-4.29-4.24l-.19-43.66c-.01-2.36,1.89-4.28,4.25-4.3l8.85-.06h0s1.97-.01,1.97-.01c1.54,0,3.02,.59,4.11,1.67l5.37,5.31c1.09,1.08,2.57,1.68,4.11,1.67l12.68-.08c1.54,0,3.02-.63,4.1-1.73l5.32-5.38c1.08-1.1,2.56-1.72,4.1-1.73Z" fill="#7e69af" fill-rule="evenodd" stroke="#000" stroke-miterlimit="10" stroke-width="3.79"/><g><rect x="27.95" y="59.08" width="293.9" height="168.16" fill="none" stroke="#000" stroke-linejoin="round" stroke-width="6.07"/><rect x="3.03" y="3.03" width="343.72" height="249.11" fill="none" stroke="#000" stroke-linejoin="round" stroke-width="6.07"/><line x1="3.03" y1="40.4" x2="346.76" y2="40.4" fill="none" stroke="#000" stroke-linejoin="round" stroke-width="6.07"/><line x1="21.72" y1="21.72" x2="34.17" y2="21.72" fill="none" stroke="#000" stroke-linejoin="round" stroke-width="6.07"/><line x1="46.63" y1="21.72" x2="59.08" y2="21.72" fill="none" stroke="#000" stroke-linejoin="round" stroke-width="6.07"/><line x1="71.54" y1="21.72" x2="84" y2="21.72" fill="none" stroke="#000" stroke-linejoin="round" stroke-width="6.07"/><polygon points="160.79 121.68 160.79 171.51 204.39 146.59 160.79 121.68" fill="#1e1e1e" stroke="#1e1e1e" stroke-linejoin="round" stroke-width="6.07"/></g></g></g></svg>
|
After Width: | Height: | Size: 2.1 KiB |
89
test/unit/lib/onboarding.test.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
import {shouldDisplayOnboarding} from '../../../src/lib/onboarding';
|
||||
|
||||
describe('unit test lib/onboarding.js', () => {
|
||||
const startDate = new Date();
|
||||
const endDate = new Date(startDate);
|
||||
endDate.setDate(endDate.getDate() + 7);
|
||||
|
||||
let user;
|
||||
let permissions;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.ONBOARDING_TEST_ACTIVE = 'true';
|
||||
process.env.ONBOARDING_TESTING_STARTING_DATE = startDate.toJSON().split('T')[0];
|
||||
process.env.ONBOARDING_TESTING_ENDING_DATE = endDate.toJSON().split('T')[0];
|
||||
|
||||
user = {id: 2, dateJoined: startDate.toJSON(), banned: false};
|
||||
permissions = {admin: false, mute_status: {}, new_scratcher: true};
|
||||
});
|
||||
|
||||
describe('#shouldDisplayOnboarding', () => {
|
||||
describe('when user is eligible to view onboarding journeys', () => {
|
||||
describe('when there is time frame for A/B testing', () => {
|
||||
test('returns true', () => {
|
||||
expect(shouldDisplayOnboarding(user, permissions)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when user is not eligible to view onboarding journeys', () => {
|
||||
describe('when feature flag is toggled off', () => {
|
||||
test('returns false', () => {
|
||||
process.env.ONBOARDING_TEST_ACTIVE = 'false';
|
||||
expect(shouldDisplayOnboarding(user, permissions)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when user is in other testing group', () => {
|
||||
test('returns false', () => {
|
||||
user.id = 1;
|
||||
expect(shouldDisplayOnboarding(user, permissions)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when user is registered outside of time frame', () => {
|
||||
test('returns false', () => {
|
||||
const currentDate = new Date();
|
||||
currentDate.setDate(currentDate.getDate() - 1);
|
||||
user.dateJoined = currentDate.toJSON();
|
||||
expect(shouldDisplayOnboarding(user, permissions)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when user is admin', () => {
|
||||
test('returns false', () => {
|
||||
permissions.admin = true;
|
||||
expect(shouldDisplayOnboarding(user, permissions)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when user is muted', () => {
|
||||
test('returns false', () => {
|
||||
permissions.mute_status = {showWarning: true};
|
||||
expect(shouldDisplayOnboarding(user, permissions)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when user is banned', () => {
|
||||
test('returns false', () => {
|
||||
user.banned = true;
|
||||
expect(shouldDisplayOnboarding(user, permissions)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when user is empty', () => {
|
||||
test('returns false', () => {
|
||||
user = {};
|
||||
expect(shouldDisplayOnboarding(user, permissions)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when permissions is empty', () => {
|
||||
test('returns false', () => {
|
||||
permissions = {};
|
||||
expect(shouldDisplayOnboarding(user, permissions)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -171,6 +171,7 @@ module.exports = {
|
|||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: {
|
||||
auto: true,
|
||||
localIdentName: '[name]_[local]_[hash:base64:5]',
|
||||
exportLocalsConvention: 'camelCase'
|
||||
},
|
||||
|
@ -284,8 +285,25 @@ module.exports = {
|
|||
'process.env.GA_ID': `"${process.env.GA_ID || 'UA-000000-01'}"`,
|
||||
'process.env.GTM_ENV_AUTH': `"${process.env.GTM_ENV_AUTH || ''}"`,
|
||||
'process.env.GTM_ID': process.env.GTM_ID ? `"${process.env.GTM_ID}"` : null,
|
||||
'process.env.USER_GUIDING_ID': `"${process.env.USER_GUIDING_ID || ''}"`,
|
||||
'process.env.SORTING_HAT_HOST': `"${process.env.SORTING_HAT_HOST || ''}"`
|
||||
'process.env.ONBOARDING_TEST_ACTIVE': `"${
|
||||
process.env.ONBOARDING_TEST_ACTIVE || false
|
||||
}"`,
|
||||
'process.env.ONBOARDING_TEST_PROJECT_IDS': `'${process.env.ONBOARDING_TEST_PROJECT_IDS || JSON.stringify(
|
||||
{
|
||||
clicker: '10128368',
|
||||
pong: '10128515',
|
||||
animateCharacter: '10128067',
|
||||
makeItFly: '114019829',
|
||||
recordSound: '1031325137',
|
||||
makeMusic: '10012676'
|
||||
}
|
||||
)}'`,
|
||||
'process.env.ONBOARDING_TESTING_STARTING_DATE': `"${
|
||||
process.env.ONBOARDING_TESTING_STARTING_DATE || '2024-01-20'
|
||||
}"`,
|
||||
'process.env.ONBOARDING_TESTING_ENDING_DATE': `"${
|
||||
process.env.ONBOARDING_TESTING_ENDING_DATE || '2030-11-20'
|
||||
}"`
|
||||
})
|
||||
])
|
||||
.concat(process.env.ANALYZE_BUNDLE === 'true' ? [
|
||||
|
|