committed by
Mrugesh Mohapatra
parent
54a9f70574
commit
f90bb69cde
@ -5,7 +5,8 @@ module.exports = {
|
||||
// '.module.css' https://regex101.com/r/VzwrKH/4
|
||||
'^(?!.*\\.module\\.css$).*\\.css$': '<rootDir>/src/__mocks__/styleMock.js',
|
||||
// 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/'],
|
||||
globals: {
|
||||
|
@ -35,6 +35,7 @@
|
||||
"react": "16",
|
||||
"react-bootstrap": "^0.32.1",
|
||||
"react-dom": "16",
|
||||
"react-ga": "^2.5.2",
|
||||
"react-helmet": "^5.2.0",
|
||||
"react-monaco-editor": "^0.14.1",
|
||||
"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 Link, { navigateTo } from 'gatsby-link';
|
||||
|
||||
import ga from '../../../analytics';
|
||||
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
|
||||
import { toggleMapModal } from '../../../redux/app';
|
||||
import Caret from '../../icons/Caret';
|
||||
@ -44,18 +45,31 @@ export class Block extends PureComponent {
|
||||
.slice(0, -1)
|
||||
.join('/');
|
||||
toggleBlock(blockDashedName);
|
||||
ga.event({
|
||||
category: 'Map Block Click',
|
||||
action: blockDashedName
|
||||
});
|
||||
return navigateTo(blockPath);
|
||||
}
|
||||
|
||||
handleChallengeClick() {
|
||||
this.props.toggleMapModal();
|
||||
handleChallengeClick(slug) {
|
||||
return () => {
|
||||
this.props.toggleMapModal();
|
||||
return ga.event({
|
||||
category: 'Map Challenge Click',
|
||||
action: slug
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
renderChallenges(challenges) {
|
||||
// TODO: Split this into a Challenge Component and add tests
|
||||
return challenges.map(challenge => (
|
||||
<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}
|
||||
</Link>
|
||||
</li>
|
||||
|
@ -1,10 +1,11 @@
|
||||
/* global graphql */
|
||||
import React, { Fragment } from 'react';
|
||||
import React, { Fragment, PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
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 MapModal from '../components/MapModal';
|
||||
|
||||
@ -12,25 +13,84 @@ import './global.css';
|
||||
import 'react-reflex/styles.css';
|
||||
import './layout.css';
|
||||
|
||||
const Layout = ({ children, data: { allChallengeNode: { edges } } }) => (
|
||||
<Fragment>
|
||||
<Helmet
|
||||
meta={[
|
||||
{ name: 'description', content: 'Sample' },
|
||||
{ name: 'keywords', content: 'sample, something' }
|
||||
]}
|
||||
/>
|
||||
<Header />
|
||||
<div className='app-wrapper'>
|
||||
<main>{children()}</main>
|
||||
</div>
|
||||
<MapModal
|
||||
nodes={edges
|
||||
.map(({ node }) => node)
|
||||
.filter(({ isPrivate }) => !isPrivate)}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
const metaKeywords = [
|
||||
'javascript',
|
||||
'js',
|
||||
'website',
|
||||
'web',
|
||||
'development',
|
||||
'free',
|
||||
'code',
|
||||
'camp',
|
||||
'course',
|
||||
'courses',
|
||||
'html',
|
||||
'css',
|
||||
'react',
|
||||
'redux',
|
||||
'api',
|
||||
'front',
|
||||
'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 = {
|
||||
children: PropTypes.func,
|
||||
|
@ -8,6 +8,7 @@ import { routerReducer as router, routerMiddleware } from 'react-router-redux';
|
||||
|
||||
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 challenge,
|
||||
@ -23,7 +24,7 @@ const rootReducer = combineReducers({
|
||||
router
|
||||
});
|
||||
|
||||
const rootEpic = combineEpics(...appEpics, ...challengeEpics);
|
||||
const rootEpic = combineEpics(analyticsEpic, ...appEpics, ...challengeEpics);
|
||||
|
||||
const epicMiddleware = createEpicMiddleware(rootEpic, {
|
||||
dependencies: {
|
||||
|
@ -115,8 +115,8 @@ class ShowClassic extends PureComponent {
|
||||
},
|
||||
pathContext: { challengeMeta }
|
||||
} = this.props;
|
||||
updateSuccessMessage(randomCompliment());
|
||||
if (prevTitle !== currentTitle) {
|
||||
updateSuccessMessage(randomCompliment());
|
||||
createFiles(files);
|
||||
initTests(tests);
|
||||
updateChallengeMeta({ ...challengeMeta, title: currentTitle });
|
||||
|
@ -5,6 +5,7 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Button, Modal } from 'react-bootstrap';
|
||||
|
||||
import ga from '../../../analytics';
|
||||
import GreenPass from './icons/GreenPass';
|
||||
|
||||
import './completion-modal.css';
|
||||
@ -57,6 +58,9 @@ export class CompletionModal extends PureComponent {
|
||||
handleKeypress,
|
||||
message
|
||||
} = this.props;
|
||||
if (isOpen) {
|
||||
ga.modalview('/completion-modal');
|
||||
}
|
||||
return (
|
||||
<Modal
|
||||
animation={false}
|
||||
|
@ -4,6 +4,7 @@ import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { Button, Modal } from 'react-bootstrap';
|
||||
|
||||
import ga from '../../../analytics';
|
||||
import { createQuestion, closeModal, isHelpModalOpenSelector } from '../redux';
|
||||
|
||||
const mapStateToProps = state => ({ isOpen: isHelpModalOpenSelector(state) });
|
||||
@ -26,6 +27,9 @@ const RSA =
|
||||
export class HelpModal extends PureComponent {
|
||||
render() {
|
||||
const { isOpen, closeHelpModal, createQuestion } = this.props;
|
||||
if (isOpen) {
|
||||
ga.modalview('/help-modal');
|
||||
}
|
||||
return (
|
||||
<Modal onHide={closeHelpModal} show={isOpen}>
|
||||
<Modal.Header
|
||||
|
@ -5,6 +5,7 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Button, Modal } from 'react-bootstrap';
|
||||
|
||||
import ga from '../../../analytics';
|
||||
import { isResetModalOpenSelector, closeModal, resetChallenge } from '../redux';
|
||||
|
||||
import './reset-modal.css';
|
||||
@ -30,6 +31,9 @@ function withActions(...fns) {
|
||||
}
|
||||
|
||||
function ResetModal({ reset, close, isOpen }) {
|
||||
if (isOpen) {
|
||||
ga.modalview('/reset-modal');
|
||||
}
|
||||
return (
|
||||
<Modal
|
||||
animation={false}
|
||||
|
@ -8544,6 +8544,13 @@ react-error-overlay@^3.0.0:
|
||||
version "3.0.0"
|
||||
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:
|
||||
version "5.2.0"
|
||||
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"
|
||||
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:
|
||||
version "16.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"
|
||||
|
Reference in New Issue
Block a user