diff --git a/src/lib/user-guiding.js b/src/lib/user-guiding.js new file mode 100644 index 000000000..8612c68ec --- /dev/null +++ b/src/lib/user-guiding.js @@ -0,0 +1,163 @@ +const api = require('./api'); + +const USER_GUIDING_ID = process.env.USER_GUIDING_ID; +const SCRIPT_ID = 'UserGuiding'; + +const CONDITIONS = {condition_list: [ + 'IsLoggedIn', + 'IsNewScratcher', + 'NotMuted' +]}; + +const getUserGuidingSnippet = () => ( + ` + (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 identifyUser = userId => { + window.userGuiding.identify(userId.toString()); +}; + +const launchSurvey = surveyId => { + window.userGuiding.launchSurvey(surveyId); +}; + +const probabilityPicker = data => { + let generatedValue = Math.random(); + + for (const tmp of data) { + if (tmp.probability < generatedValue) { + generatedValue -= tmp.probability; + } else { + return tmp.prompt; + } + } +}; + +const activateUserGuiding = (userId, callback) => { + if (!document.getElementById(SCRIPT_ID)) { + const userGuidingScript = document.createElement('script'); + userGuidingScript.id = SCRIPT_ID; + userGuidingScript.innerHTML = getUserGuidingSnippet(); + document.head.insertBefore(userGuidingScript, document.head.firstChild); + + window.userGuidingSettings = { + disablePageViewAutoCapture: true + }; + + window.userGuidingLayer.push({ + event: 'onload', + fn: () => identifyUser(userId) + }); + + window.userGuidingLayer.push({ + event: 'onIdentificationComplete', + fn: callback + }); + } else if (window.userGuiding) { + callback(); + } +}; + +const displayUserGuiding = (userId, permissions, guideId, callback) => ( + 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; + } + + const {result} = JSON.stringify(body); + if (result) { + activateUserGuiding(userId, callback); + } + }) +); + +const loadCompetenceSurvey = (userId, permissions) => { + const COMPETENCE_SURVEY_ID = 3045; + + displayUserGuiding( + userId, + permissions, + COMPETENCE_SURVEY_ID, + () => launchSurvey(COMPETENCE_SURVEY_ID) + ); +}; + +const loadAutonomySurvey = (userId, permissions) => { + const AUTONOMY_SURVEY_ID = 3048; + + displayUserGuiding( + userId, + permissions, + AUTONOMY_SURVEY_ID, + () => launchSurvey(AUTONOMY_SURVEY_ID) + ); +}; + +const loadRelationshipsSurvey = (userId, permissions) => { + const RELATIONSHIP_SURVEY_ID = 3049; + + displayUserGuiding( + userId, + permissions, + RELATIONSHIP_SURVEY_ID, + () => launchSurvey(RELATIONSHIP_SURVEY_ID) + ); +}; + +const loadJoySurvey = (userId, permissions) => { + const JOY_SURVEY_ID = 3050; + + displayUserGuiding( + userId, + permissions, + JOY_SURVEY_ID, + () => launchSurvey(JOY_SURVEY_ID) + ); +}; + +const loadRandomPrompt = (userId, permissions, data) => { + const prompt = probabilityPicker(data); + + prompt(userId, permissions); +}; + +module.exports = { + loadCompetenceSurvey, + loadAutonomySurvey, + loadRelationshipsSurvey, + loadJoySurvey, + loadRandomPrompt +}; diff --git a/src/views/preview/comment/compose-comment.jsx b/src/views/preview/comment/compose-comment.jsx index cfa706dec..adba435d6 100644 --- a/src/views/preview/comment/compose-comment.jsx +++ b/src/views/preview/comment/compose-comment.jsx @@ -360,6 +360,7 @@ class ComposeComment extends React.Component { type="textarea" value={this.state.message} onInput={this.handleInput} + onClick={this.props.onClick} autoFocus={this.props.isReply} /> @@ -437,6 +438,7 @@ ComposeComment.propTypes = { }), onAddComment: PropTypes.func, onCancel: PropTypes.func, + onClick: PropTypes.func, parentId: PropTypes.number, postURI: PropTypes.string, user: PropTypes.shape({ diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index a1578d102..ced00b5da 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -34,11 +34,13 @@ 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 {loadAutonomySurvey} = require('../../lib/user-guiding.js'); const projectShape = require('./projectshape.jsx').projectShape; require('./preview.scss'); const frameless = require('../../lib/frameless'); +const {useState, useCallback} = require('react'); // disable enter key submission on formsy input fields; otherwise formsy thinks // we meant to trigger the "See inside" button. Instead, treat these keypresses @@ -127,6 +129,7 @@ const PreviewPresentation = ({ showCloudDataAlert, showCloudDataAndVideoAlert, showUsernameBlockAlert, + permissions, projectHost, projectId, projectInfo, @@ -140,9 +143,11 @@ const PreviewPresentation = ({ showEmailConfirmationBanner, singleCommentId, socialOpen, + user, userOwnsProject, visibilityInfo }) => { + const [interactionWithComment, setInteractionWithComment] = useState(false); 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 || @@ -215,6 +220,14 @@ const PreviewPresentation = ({ ))} ); + + const onCommentClick = useCallback(() => { + if (!interactionWithComment && user) { + setInteractionWithComment(!interactionWithComment); + loadAutonomySurvey(user.id, permissions); + } + }, [interactionWithComment, user]); + return (
{showEmailConfirmationModal && ) : ( /* TODO add box for signing in to leave a comment */ @@ -784,6 +798,7 @@ PreviewPresentation.propTypes = { onUpdateProjectThumbnail: PropTypes.func, originalInfo: projectShape, parentInfo: projectShape, + permissions: PropTypes.object, projectHost: PropTypes.string, projectId: PropTypes.string, projectInfo: projectShape, @@ -800,6 +815,17 @@ PreviewPresentation.propTypes = { showUsernameBlockAlert: PropTypes.bool, singleCommentId: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]), socialOpen: PropTypes.bool, + user: PropTypes.shape({ + id: PropTypes.number, + banned: PropTypes.bool, + vpn_required: PropTypes.bool, + username: PropTypes.string, + token: PropTypes.string, + thumbnailUrl: PropTypes.string, + dateJoined: PropTypes.string, + email: PropTypes.string, + classroomId: PropTypes.string + }), userOwnsProject: PropTypes.bool, visibilityInfo: PropTypes.shape({ censored: PropTypes.bool, diff --git a/src/views/preview/project-view.jsx b/src/views/preview/project-view.jsx index bc53feb39..bd06654c6 100644 --- a/src/views/preview/project-view.jsx +++ b/src/views/preview/project-view.jsx @@ -25,6 +25,12 @@ 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 { + loadRelationshipsSurvey, + loadCompetenceSurvey, + loadJoySurvey, + loadRandomPrompt +} = require('../../lib/user-guiding.js'); const sessionActions = require('../../redux/session.js'); const {selectProjectCommentsGloballyEnabled, selectIsTotallyNormal} = require('../../redux/session'); @@ -83,6 +89,7 @@ class Preview extends React.Component { 'handleUpdateProjectId', 'handleUpdateProjectTitle', 'handleToggleComments', + 'handleLoadRandomPrompt', 'initCounts', 'pushHistory', 'renderLogin', @@ -627,6 +634,7 @@ class Preview extends React.Component { justRemixed: false, justShared: true }); + loadRelationshipsSurvey(this.props.user.id, this.props.permissions); } handleShareAttempt () { this.setState({ @@ -694,6 +702,18 @@ class Preview extends React.Component { this.props.user.token ); } + handleLoadRandomPrompt () { + loadRandomPrompt(this.props.user.id, this.props.permissions, [ + { + prompt: loadCompetenceSurvey, + probability: 0.5 + }, + { + prompt: loadJoySurvey, + probability: 0.5 + } + ]); + } initCounts (favorites, loves) { this.setState({ favoriteCount: favorites, @@ -786,6 +806,7 @@ class Preview extends React.Component { moreCommentsToLoad={this.props.moreCommentsToLoad} originalInfo={this.props.original} parentInfo={this.props.parent} + permissions={this.props.permissions} projectHost={this.props.projectHost} projectId={this.state.projectId} projectInfo={this.props.projectInfo} @@ -802,6 +823,7 @@ class Preview extends React.Component { showUsernameBlockAlert={this.state.showUsernameBlockAlert} singleCommentId={this.state.singleCommentId} socialOpen={this.state.socialOpen} + user={this.props.user} userOwnsProject={this.props.userOwnsProject} visibilityInfo={this.props.visibilityInfo} onAddComment={this.handleAddComment} @@ -879,6 +901,7 @@ class Preview extends React.Component { onUpdateProjectId={this.handleUpdateProjectId} onUpdateProjectThumbnail={this.props.handleUpdateProjectThumbnail} onUpdateProjectTitle={this.handleUpdateProjectTitle} + onLoadRandomPrompt={this.handleLoadRandomPrompt} /> )} {this.props.registrationOpen && ( @@ -957,6 +980,7 @@ Preview.propTypes = { moreCommentsToLoad: PropTypes.bool, original: projectShape, parent: projectShape, + permissions: PropTypes.object, playerMode: PropTypes.bool, projectHost: PropTypes.string.isRequired, projectInfo: projectShape, @@ -1076,6 +1100,7 @@ const mapStateToProps = state => { moreCommentsToLoad: state.comments.moreCommentsToLoad, original: state.preview.original, parent: state.preview.parent, + permissions: state.permissions, playerMode: state.scratchGui.mode.isPlayerOnly, projectInfo: state.preview.projectInfo, projectNotAvailable: state.preview.projectNotAvailable, diff --git a/webpack.config.js b/webpack.config.js index 61db55450..aec11a587 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -279,7 +279,9 @@ module.exports = { 'process.env.DEBUG': Boolean(process.env.DEBUG), '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.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 || 'http://127.0.0.1:7676'}"` }) ]) .concat(process.env.ANALYZE_BUNDLE === 'true' ? [