committed by
Mrugesh Mohapatra
parent
54a9f70574
commit
f90bb69cde
@ -5,7 +5,8 @@ module.exports = {
|
|||||||
// '.module.css' https://regex101.com/r/VzwrKH/4
|
// '.module.css' https://regex101.com/r/VzwrKH/4
|
||||||
'^(?!.*\\.module\\.css$).*\\.css$': '<rootDir>/src/__mocks__/styleMock.js',
|
'^(?!.*\\.module\\.css$).*\\.css$': '<rootDir>/src/__mocks__/styleMock.js',
|
||||||
// CSS Modules - match files that end with 'module.css'
|
// CSS Modules - match files that end with 'module.css'
|
||||||
'\\.module\\.css$': 'identity-obj-proxy'
|
'\\.module\\.css$': 'identity-obj-proxy',
|
||||||
|
analytics: '<rootDir>/src/__mocks__/analyticsMock.js'
|
||||||
},
|
},
|
||||||
testPathIgnorePatterns: ['/node_modules/', '<rootDir>/.cache/'],
|
testPathIgnorePatterns: ['/node_modules/', '<rootDir>/.cache/'],
|
||||||
globals: {
|
globals: {
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
"react": "16",
|
"react": "16",
|
||||||
"react-bootstrap": "^0.32.1",
|
"react-bootstrap": "^0.32.1",
|
||||||
"react-dom": "16",
|
"react-dom": "16",
|
||||||
|
"react-ga": "^2.5.2",
|
||||||
"react-helmet": "^5.2.0",
|
"react-helmet": "^5.2.0",
|
||||||
"react-monaco-editor": "^0.14.1",
|
"react-monaco-editor": "^0.14.1",
|
||||||
"react-redux": "^5.0.7",
|
"react-redux": "^5.0.7",
|
||||||
|
3
packages/learn/src/__mocks__/analyticsMock.js
Normal file
3
packages/learn/src/__mocks__/analyticsMock.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default {
|
||||||
|
event: () => {}
|
||||||
|
};
|
15
packages/learn/src/analytics/analytics-epic.js
Normal file
15
packages/learn/src/analytics/analytics-epic.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { tap, ignoreElements } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import ga from './';
|
||||||
|
|
||||||
|
export default function analyticsEpic(action$) {
|
||||||
|
return action$.pipe(
|
||||||
|
tap(({ type }) => {
|
||||||
|
ga.event({
|
||||||
|
category: 'Redux Action',
|
||||||
|
action: type
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
ignoreElements()
|
||||||
|
);
|
||||||
|
}
|
5
packages/learn/src/analytics/index.js
Normal file
5
packages/learn/src/analytics/index.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import ReactGA from 'react-ga';
|
||||||
|
|
||||||
|
ReactGA.initialize('UA-55446531-10');
|
||||||
|
|
||||||
|
export default ReactGA;
|
@ -5,6 +5,7 @@ import { connect } from 'react-redux';
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import Link, { navigateTo } from 'gatsby-link';
|
import Link, { navigateTo } from 'gatsby-link';
|
||||||
|
|
||||||
|
import ga from '../../../analytics';
|
||||||
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
|
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
|
||||||
import { toggleMapModal } from '../../../redux/app';
|
import { toggleMapModal } from '../../../redux/app';
|
||||||
import Caret from '../../icons/Caret';
|
import Caret from '../../icons/Caret';
|
||||||
@ -44,18 +45,31 @@ export class Block extends PureComponent {
|
|||||||
.slice(0, -1)
|
.slice(0, -1)
|
||||||
.join('/');
|
.join('/');
|
||||||
toggleBlock(blockDashedName);
|
toggleBlock(blockDashedName);
|
||||||
|
ga.event({
|
||||||
|
category: 'Map Block Click',
|
||||||
|
action: blockDashedName
|
||||||
|
});
|
||||||
return navigateTo(blockPath);
|
return navigateTo(blockPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChallengeClick() {
|
handleChallengeClick(slug) {
|
||||||
this.props.toggleMapModal();
|
return () => {
|
||||||
|
this.props.toggleMapModal();
|
||||||
|
return ga.event({
|
||||||
|
category: 'Map Challenge Click',
|
||||||
|
action: slug
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
renderChallenges(challenges) {
|
renderChallenges(challenges) {
|
||||||
// TODO: Split this into a Challenge Component and add tests
|
// TODO: Split this into a Challenge Component and add tests
|
||||||
return challenges.map(challenge => (
|
return challenges.map(challenge => (
|
||||||
<li className='map-challenge-title' key={challenge.dashedName}>
|
<li className='map-challenge-title' key={challenge.dashedName}>
|
||||||
<Link onClick={this.handleChallengeClick} to={challenge.fields.slug}>
|
<Link
|
||||||
|
onClick={this.handleChallengeClick(challenge.fields.slug)}
|
||||||
|
to={challenge.fields.slug}
|
||||||
|
>
|
||||||
{challenge.title}
|
{challenge.title}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
/* global graphql */
|
/* global graphql */
|
||||||
import React, { Fragment } from 'react';
|
import React, { Fragment, PureComponent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
import { AllChallengeNode } from '../redux/propTypes';
|
import ga from '../analytics';
|
||||||
|
|
||||||
|
import { AllChallengeNode } from '../redux/propTypes';
|
||||||
import Header from '../components/Header';
|
import Header from '../components/Header';
|
||||||
import MapModal from '../components/MapModal';
|
import MapModal from '../components/MapModal';
|
||||||
|
|
||||||
@ -12,25 +13,84 @@ import './global.css';
|
|||||||
import 'react-reflex/styles.css';
|
import 'react-reflex/styles.css';
|
||||||
import './layout.css';
|
import './layout.css';
|
||||||
|
|
||||||
const Layout = ({ children, data: { allChallengeNode: { edges } } }) => (
|
const metaKeywords = [
|
||||||
<Fragment>
|
'javascript',
|
||||||
<Helmet
|
'js',
|
||||||
meta={[
|
'website',
|
||||||
{ name: 'description', content: 'Sample' },
|
'web',
|
||||||
{ name: 'keywords', content: 'sample, something' }
|
'development',
|
||||||
]}
|
'free',
|
||||||
/>
|
'code',
|
||||||
<Header />
|
'camp',
|
||||||
<div className='app-wrapper'>
|
'course',
|
||||||
<main>{children()}</main>
|
'courses',
|
||||||
</div>
|
'html',
|
||||||
<MapModal
|
'css',
|
||||||
nodes={edges
|
'react',
|
||||||
.map(({ node }) => node)
|
'redux',
|
||||||
.filter(({ isPrivate }) => !isPrivate)}
|
'api',
|
||||||
/>
|
'front',
|
||||||
</Fragment>
|
'back',
|
||||||
);
|
'end',
|
||||||
|
'learn',
|
||||||
|
'tutorial',
|
||||||
|
'programming'
|
||||||
|
];
|
||||||
|
|
||||||
|
class Layout extends PureComponent {
|
||||||
|
state = {
|
||||||
|
location: ''
|
||||||
|
};
|
||||||
|
componentDidMount() {
|
||||||
|
const url = window.location.pathname + window.location.search;
|
||||||
|
ga.pageview(url);
|
||||||
|
/* eslint-disable react/no-did-mount-set-state */
|
||||||
|
// this is for local location tracking only, no re-rendering required
|
||||||
|
this.setState(state => ({
|
||||||
|
...state,
|
||||||
|
location: url
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
componentDidUpdate() {
|
||||||
|
const url = window.location.pathname + window.location.search;
|
||||||
|
if (url !== this.state.location) {
|
||||||
|
ga.pageview(url);
|
||||||
|
/* eslint-disable react/no-did-update-set-state */
|
||||||
|
// this is for local location tracking only, no re-rendering required
|
||||||
|
this.setState(state => ({
|
||||||
|
...state,
|
||||||
|
location: url
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const { children, data: { allChallengeNode: { edges } } } = this.props;
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Helmet
|
||||||
|
meta={[
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
content:
|
||||||
|
'Learn to code with free online courses, programming ' +
|
||||||
|
'projects, and interview preparation for developer jobs.'
|
||||||
|
},
|
||||||
|
{ name: 'keywords', content: metaKeywords.join(', ') }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Header />
|
||||||
|
<div className='app-wrapper'>
|
||||||
|
<main>{children()}</main>
|
||||||
|
</div>
|
||||||
|
<MapModal
|
||||||
|
nodes={edges
|
||||||
|
.map(({ node }) => node)
|
||||||
|
.filter(({ isPrivate }) => !isPrivate)}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Layout.propTypes = {
|
Layout.propTypes = {
|
||||||
children: PropTypes.func,
|
children: PropTypes.func,
|
||||||
|
@ -8,6 +8,7 @@ import { routerReducer as router, routerMiddleware } from 'react-router-redux';
|
|||||||
|
|
||||||
import { reducer as formReducer } from 'redux-form';
|
import { reducer as formReducer } from 'redux-form';
|
||||||
|
|
||||||
|
import analyticsEpic from '../analytics/analytics-epic';
|
||||||
import { reducer as app, epics as appEpics } from './app';
|
import { reducer as app, epics as appEpics } from './app';
|
||||||
import {
|
import {
|
||||||
reducer as challenge,
|
reducer as challenge,
|
||||||
@ -23,7 +24,7 @@ const rootReducer = combineReducers({
|
|||||||
router
|
router
|
||||||
});
|
});
|
||||||
|
|
||||||
const rootEpic = combineEpics(...appEpics, ...challengeEpics);
|
const rootEpic = combineEpics(analyticsEpic, ...appEpics, ...challengeEpics);
|
||||||
|
|
||||||
const epicMiddleware = createEpicMiddleware(rootEpic, {
|
const epicMiddleware = createEpicMiddleware(rootEpic, {
|
||||||
dependencies: {
|
dependencies: {
|
||||||
|
@ -115,8 +115,8 @@ class ShowClassic extends PureComponent {
|
|||||||
},
|
},
|
||||||
pathContext: { challengeMeta }
|
pathContext: { challengeMeta }
|
||||||
} = this.props;
|
} = this.props;
|
||||||
updateSuccessMessage(randomCompliment());
|
|
||||||
if (prevTitle !== currentTitle) {
|
if (prevTitle !== currentTitle) {
|
||||||
|
updateSuccessMessage(randomCompliment());
|
||||||
createFiles(files);
|
createFiles(files);
|
||||||
initTests(tests);
|
initTests(tests);
|
||||||
updateChallengeMeta({ ...challengeMeta, title: currentTitle });
|
updateChallengeMeta({ ...challengeMeta, title: currentTitle });
|
||||||
|
@ -5,6 +5,7 @@ import { connect } from 'react-redux';
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { Button, Modal } from 'react-bootstrap';
|
import { Button, Modal } from 'react-bootstrap';
|
||||||
|
|
||||||
|
import ga from '../../../analytics';
|
||||||
import GreenPass from './icons/GreenPass';
|
import GreenPass from './icons/GreenPass';
|
||||||
|
|
||||||
import './completion-modal.css';
|
import './completion-modal.css';
|
||||||
@ -57,6 +58,9 @@ export class CompletionModal extends PureComponent {
|
|||||||
handleKeypress,
|
handleKeypress,
|
||||||
message
|
message
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
if (isOpen) {
|
||||||
|
ga.modalview('/completion-modal');
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
animation={false}
|
animation={false}
|
||||||
|
@ -4,6 +4,7 @@ import { bindActionCreators } from 'redux';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Button, Modal } from 'react-bootstrap';
|
import { Button, Modal } from 'react-bootstrap';
|
||||||
|
|
||||||
|
import ga from '../../../analytics';
|
||||||
import { createQuestion, closeModal, isHelpModalOpenSelector } from '../redux';
|
import { createQuestion, closeModal, isHelpModalOpenSelector } from '../redux';
|
||||||
|
|
||||||
const mapStateToProps = state => ({ isOpen: isHelpModalOpenSelector(state) });
|
const mapStateToProps = state => ({ isOpen: isHelpModalOpenSelector(state) });
|
||||||
@ -26,6 +27,9 @@ const RSA =
|
|||||||
export class HelpModal extends PureComponent {
|
export class HelpModal extends PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const { isOpen, closeHelpModal, createQuestion } = this.props;
|
const { isOpen, closeHelpModal, createQuestion } = this.props;
|
||||||
|
if (isOpen) {
|
||||||
|
ga.modalview('/help-modal');
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Modal onHide={closeHelpModal} show={isOpen}>
|
<Modal onHide={closeHelpModal} show={isOpen}>
|
||||||
<Modal.Header
|
<Modal.Header
|
||||||
|
@ -5,6 +5,7 @@ import { connect } from 'react-redux';
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { Button, Modal } from 'react-bootstrap';
|
import { Button, Modal } from 'react-bootstrap';
|
||||||
|
|
||||||
|
import ga from '../../../analytics';
|
||||||
import { isResetModalOpenSelector, closeModal, resetChallenge } from '../redux';
|
import { isResetModalOpenSelector, closeModal, resetChallenge } from '../redux';
|
||||||
|
|
||||||
import './reset-modal.css';
|
import './reset-modal.css';
|
||||||
@ -30,6 +31,9 @@ function withActions(...fns) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ResetModal({ reset, close, isOpen }) {
|
function ResetModal({ reset, close, isOpen }) {
|
||||||
|
if (isOpen) {
|
||||||
|
ga.modalview('/reset-modal');
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
animation={false}
|
animation={false}
|
||||||
|
@ -8544,6 +8544,13 @@ react-error-overlay@^3.0.0:
|
|||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-3.0.0.tgz#c2bc8f4d91f1375b3dad6d75265d51cd5eeaf655"
|
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-3.0.0.tgz#c2bc8f4d91f1375b3dad6d75265d51cd5eeaf655"
|
||||||
|
|
||||||
|
react-ga@^2.5.2:
|
||||||
|
version "2.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-2.5.2.tgz#1574b26e30ed668e4e74735527314393b22c55a9"
|
||||||
|
optionalDependencies:
|
||||||
|
prop-types "^15.6.0"
|
||||||
|
react "^15.6.2 || ^16.0"
|
||||||
|
|
||||||
react-helmet@^5.2.0:
|
react-helmet@^5.2.0:
|
||||||
version "5.2.0"
|
version "5.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-5.2.0.tgz#a81811df21313a6d55c5f058c4aeba5d6f3d97a7"
|
resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-5.2.0.tgz#a81811df21313a6d55c5f058c4aeba5d6f3d97a7"
|
||||||
@ -8717,6 +8724,15 @@ react@^15.6.0:
|
|||||||
object-assign "^4.1.0"
|
object-assign "^4.1.0"
|
||||||
prop-types "^15.5.10"
|
prop-types "^15.5.10"
|
||||||
|
|
||||||
|
"react@^15.6.2 || ^16.0":
|
||||||
|
version "16.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react/-/react-16.3.2.tgz#fdc8420398533a1e58872f59091b272ce2f91ea9"
|
||||||
|
dependencies:
|
||||||
|
fbjs "^0.8.16"
|
||||||
|
loose-envify "^1.1.0"
|
||||||
|
object-assign "^4.1.1"
|
||||||
|
prop-types "^15.6.0"
|
||||||
|
|
||||||
react@^16.0.0:
|
react@^16.0.0:
|
||||||
version "16.2.0"
|
version "16.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"
|
||||||
|
Reference in New Issue
Block a user