Skip to content

Commit

Permalink
Merge pull request #1088 from ParabolInc/segment-hotfix
Browse files Browse the repository at this point in the history
re-add segment metadata fix #1086 fix #1047
  • Loading branch information
jordanh authored Jun 29, 2017
2 parents 80797c4 + 1173540 commit 662a6d3
Show file tree
Hide file tree
Showing 30 changed files with 986 additions and 820 deletions.
18 changes: 17 additions & 1 deletion src/server/createSSR.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import {renderToStaticMarkup} from 'react-dom/server';
import {applyMiddleware, createStore} from 'redux';
import thunkMiddleware from 'redux-thunk';
import makeSegmentSnippet from '@segment/snippet';
import getWebpackPublicPath from 'server/utils/getWebpackPublicPath';
import makeReducer from 'universal/redux/makeReducer';
import printStyles from 'universal/styles/theme/printStyles';
Expand Down Expand Up @@ -40,17 +41,32 @@ export default function createSSR(req, res) {
}
res.send(cachedPage);
} else {
/*
* When segment.io is configured during development, load the segment
* snippet here. For production use, refer to the Html.js component.
*/
const segKey = process.env.SEGMENT_WRITE_KEY;
const segmentSnippet = segKey && `
<script>
${makeSegmentSnippet.min({
host: 'cdn.segment.com',
apiKey: segKey
})}
</script>
`;

const devHtml = `
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="/static/css/font-awesome.css"/>
</head>
<body>
<body>
<div id="root"></div>
<script src="/static/vendors.dll.js"></script>
<script src="/static/app.js"></script>
<script>${clientKeyLoader}</script>
${segmentSnippet}
</body>
</html>
`;
Expand Down
66 changes: 23 additions & 43 deletions src/universal/components/Action/Action.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import {css} from 'aphrodite-local-styles/no-important';
import PropTypes from 'prop-types';
import React from 'react';
import withStyles from 'universal/styles/withStyles';
import {css} from 'aphrodite-local-styles/no-important';
import Toast from 'universal/modules/toast/containers/Toast/Toast';
import {Route, Switch} from 'react-router-dom';
import LandingContainer from 'universal/modules/landing/containers/Landing/LandingContainer';
import AsyncRoute from 'universal/components/AsyncRoute/AsyncRoute';
import LandingContainer from 'universal/modules/landing/containers/Landing/LandingContainer';
import Toast from 'universal/modules/toast/containers/Toast/Toast';
import withStyles from 'universal/styles/withStyles';

const socketRoute = () => System.import('universal/components/SocketRoute/SocketRoute');
const invoice = () => System.import('universal/modules/invoice/containers/InvoiceContainer/InvoiceContainer');
const meetingSummary = () => System.import('universal/modules/summary/containers/MeetingSummary/MeetingSummaryContainer');
const welcome = () => System.import('universal/modules/welcome/containers/Welcome/Welcome');
const graphql = () => System.import('universal/modules/admin/containers/Graphql/GraphqlContainer');
const impersonate = () => System.import('universal/modules/admin/containers/Impersonate/ImpersonateContainer');
const invitation = () => System.import('universal/modules/invitation/containers/Invitation/InvitationContainer');
const signout = () => System.import('universal/containers/Signout/SignoutContainer');
const notFound = () => System.import('universal/components/NotFound/NotFound');

const Action = (props) => {
const {styles} = props;
Expand All @@ -14,45 +24,15 @@ const Action = (props) => {
<Toast />
<Switch>
<Route exact path="/" component={LandingContainer} />
<AsyncRoute
isPrivate
path="(/me|/meeting|/newteam|/team)"
mod={() => System.import('universal/components/SocketRoute/SocketRoute')}
/>
<AsyncRoute
isPrivate
path="/invoice/:invoiceId"
mod={() => System.import('universal/modules/invoice/containers/InvoiceContainer/InvoiceContainer')}
/>
<AsyncRoute
isPrivate
path="/summary/:meetingId"
mod={() => System.import('universal/modules/summary/containers/MeetingSummary/MeetingSummaryContainer')}
/>
<AsyncRoute
isPrivate
path="/welcome"
mod={() => System.import('universal/modules/welcome/containers/Welcome/Welcome')}
/>
<AsyncRoute
path="/admin/graphql"
mod={() => System.import('universal/modules/admin/containers/Graphql/GraphqlContainer')}
/>
<AsyncRoute
path="/admin/impersonate/:newUserId"
mod={() => System.import('universal/modules/admin/containers/Impersonate/ImpersonateContainer')}
/>
<AsyncRoute
path="/invitation/:id"
mod={() => System.import('universal/modules/invitation/containers/Invitation/InvitationContainer')}
/>
<AsyncRoute
path="/signout"
mod={() => System.import('universal/containers/Signout/SignoutContainer')}
/>
<AsyncRoute
mod={() => System.import('universal/components/NotFound/NotFound')}
/>
<AsyncRoute isAbstract isPrivate path="(/me|/meeting|/newteam|/team)" mod={socketRoute} />
<AsyncRoute isPrivate path="/invoice/:invoiceId" mod={invoice} />
<AsyncRoute isPrivate path="/summary/:meetingId" mod={meetingSummary} />
<AsyncRoute isPrivate path="/welcome" mod={welcome} />
<AsyncRoute path="/admin/graphql" mod={graphql} />
<AsyncRoute path="/admin/impersonate/:newUserId" mod={impersonate} />
<AsyncRoute path="/invitation/:id" mod={invitation} />
<AsyncRoute mod={signout} />
<AsyncRoute mod={notFound} />
</Switch>
</div>
);
Expand Down
27 changes: 19 additions & 8 deletions src/universal/components/AsyncRoute/AsyncRoute.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,29 @@ import {Route} from 'react-router-dom';
import PropTypes from 'prop-types';
import Bundle from '../Bundle/Bundle';

const AsyncRoute = ({mod, exact, path, isPrivate, ...extra}) => (
<Route
exact={exact} path={path}
render={({history, location, match}) => (
<Bundle extra={extra} history={history} isPrivate={isPrivate} location={location} match={match} mod={mod} />
const AsyncRoute = ({mod, exact, path, isAbstract, isPrivate, extraProps}) => {
return (
<Route
exact={exact} path={path}
render={({history, location, match}) => (
<Bundle
isAbstractRoute={isAbstract}
extraProps={extraProps}
history={history}
isPrivate={isPrivate}
location={location}
match={match}
mod={mod}
/>
)}
/>
);
/>
);
};

AsyncRoute.propTypes = {
exact: PropTypes.bool,
extra: PropTypes.object,
extraProps: PropTypes.object,
isAbstract: PropTypes.bool,
isPrivate: PropTypes.bool,
mod: PropTypes.func.isRequired,
path: PropTypes.string
Expand Down
52 changes: 46 additions & 6 deletions src/universal/components/Bundle/Bundle.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import requireAuth from 'universal/decorators/requireAuth/requireAuth';
import {segmentEventPage} from 'universal/redux/segmentActions';

const updateAnalyticsPage = (dispatch, lastPath, nextPath, title, params) => {
if (typeof document === 'undefined' || typeof window.analytics === 'undefined') return;
const properties = {
title,
referrer: lastPath,
path: nextPath,
params
};
dispatch(segmentEventPage(title, null, properties));
};

class Bundle extends Component {
static contextTypes = {
analytics: PropTypes.object,
store: PropTypes.object
};

static propTypes = {
extra: PropTypes.object,
extraProps: PropTypes.object,
history: PropTypes.object.isRequired,
isAbstractRoute: PropTypes.bool,
isPrivate: PropTypes.bool,
location: PropTypes.object.isRequired,
match: PropTypes.object,
Expand All @@ -24,13 +38,39 @@ class Bundle extends Component {
this.loadMod(this.props);
}

componentDidMount() {
const {location: {pathname: nextPath}, isAbstractRoute, match: {params}} = this.props;
if (!isAbstractRoute) {
const {store: {dispatch}} = this.context;
// can't use setTimeout, since react rendering is not guaranteed sync
// use requestIdleCallback to ensure that rendering eg '/me' has completed
window.requestIdleCallback(() => {
const {analytics: {lastPath, title}} = this.context;
updateAnalyticsPage(dispatch, lastPath, nextPath, title, params);
});
}
}

componentWillReceiveProps(nextProps) {
const {mod} = this.props;
if (mod !== nextProps.mod) {
const {mod} = nextProps;
if (mod !== this.props.mod) {
this.loadMod(nextProps);
}
}

componentDidUpdate(prevProps) {
// use cDU to allow helmet to update the document title for the subcomponents
const {isAbstractRoute} = this.props;
if (!isAbstractRoute) {
const {location: {pathname: nextPath}, match: {params}} = this.props;
const {location: {pathname: lastPath}} = prevProps;
if (lastPath !== nextPath) {
const {store: {dispatch}, analytics: {title}} = this.context;
updateAnalyticsPage(dispatch, lastPath, nextPath, title, params);
}
}
}

loadMod(props) {
this.setState({Mod: null});
const {isPrivate, mod} = props;
Expand All @@ -48,8 +88,8 @@ class Bundle extends Component {
render() {
const {Mod} = this.state;
if (!Mod) return null;
const {history, location, match, extra} = this.props;
return <Mod {...extra} history={history} location={location} match={match} />;
const {history, location, match, extraProps} = this.props;
return <Mod {...extraProps} history={history} location={location} match={match} />;
}
}

Expand Down
13 changes: 7 additions & 6 deletions src/universal/components/DashboardWrapper/DashboardWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ import DashSidebar from 'universal/components/Dashboard/DashSidebar';
import DashLayoutContainer from 'universal/containers/DashLayoutContainer/DashLayoutContainer';
import AsyncRoute from 'universal/components/AsyncRoute/AsyncRoute';

const userDashboard = () => System.import('universal/modules/userDashboard/components/UserDashboard/UserDashboard');
const teamContainer = () => System.import('universal/modules/teamDashboard/containers/Team/TeamContainer');
const newTeam = () => System.import('universal/modules/newTeam/containers/NewTeamForm/NewTeamFormContainer');

const DashboardWrapper = () => {
return (
<DashLayoutContainer>
<DashSidebar />
<AsyncRoute path="/me" mod={() => System.import('universal/modules/userDashboard/components/UserDashboard/UserDashboard')} />
<AsyncRoute path="/team/:teamId" mod={() => System.import('universal/modules/teamDashboard/containers/Team/TeamContainer')} />
<AsyncRoute
path="/newteam/:newOrg?"
mod={() => System.import('universal/modules/newTeam/containers/NewTeamForm/NewTeamFormContainer')}
/>
<AsyncRoute isAbstract path="/me" mod={userDashboard} />
<AsyncRoute isAbstract path="/team/:teamId" mod={teamContainer} />
<AsyncRoute path="/newteam/:newOrg?" mod={newTeam} />
</DashLayoutContainer>
);
};
Expand Down
27 changes: 27 additions & 0 deletions src/universal/components/ParabolHelmet/ParabolHelmet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import Helmet from 'react-helmet';

export default class ParabolHelmet extends Component {
static contextTypes = {
analytics: PropTypes.object
};

static propTypes = {
title: PropTypes.string
};

componentDidMount() {
this.context.analytics.title = this.props.title;
}

componentWillReceiveProps(nextProps) {
if (nextProps.title !== this.props.title) {
this.context.analytics.title = nextProps.title;
}
}

render() {
return <Helmet {...this.props} />;
}
}
11 changes: 11 additions & 0 deletions src/universal/components/ProjectEditor/Draft.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/universal/components/ProjectEditor/ProjectEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import withKeyboardShortcuts from './withKeyboardShortcuts';
import withLinks from './withLinks';
import withSuggestions from './withSuggestions';
import entitizeText from 'universal/utils/draftjs/entitizeText';
import './Draft.css';

class ProjectEditor extends Component {

Expand Down
48 changes: 15 additions & 33 deletions src/universal/components/SocketRoute/SocketRoute.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,35 @@
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import React from 'react';
import {DragDropContext as dragDropContext} from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import {Switch} from 'react-router-dom';
import {socketClusterReducer} from 'redux-socket-cluster';
import AsyncRoute from 'universal/components/AsyncRoute/AsyncRoute';
import socketWithPresence from 'universal/decorators/socketWithPresence/socketWithPresence';
import withReducer from '../../decorators/withReducer/withReducer';
import withAsync from 'react-async-hoc';

const parentMod = () => System.import('universal/components/DashboardWrapper/DashboardWrapper');
const meetingMod = () => System.import('universal/modules/meeting/containers/MeetingContainer/MeetingContainer');

class SocketRoute extends Component {
shouldComponentUpdate() {
return false;
}
render() {
return (
<Switch>
<AsyncRoute
path="(/me|/newteam|/team)"
mod={parentMod}
/>
<AsyncRoute
path="/meeting/:teamId/:localPhase?/:localPhaseItem?"
mod={meetingMod}
/>
</Switch>
);
}
}
const dashWrapper = () => System.import('universal/components/DashboardWrapper/DashboardWrapper');
const meetingContainer = () => System.import('universal/modules/meeting/containers/MeetingContainer/MeetingContainer');

const SocketRoute = () => {
return (
<Switch>
<AsyncRoute isAbstract path="(/me|/newteam|/team)" mod={dashWrapper} />
<AsyncRoute path="/meeting/:teamId/:localPhase?/:localPhaseItem?" mod={meetingContainer} />
</Switch>
);
};

SocketRoute.propTypes = {
match: PropTypes.object.isRequired
};

const fetchStyles = {
'/static/css/Draft.css': () => ({stylesLoaded: true})
};

export default
withAsync(undefined, fetchStyles)(
withReducer({socket: socketClusterReducer})(
dragDropContext(HTML5Backend)(
socketWithPresence(
SocketRoute
)
withReducer({socket: socketClusterReducer})(
dragDropContext(HTML5Backend)(
socketWithPresence(
SocketRoute
)
)
);
Loading

0 comments on commit 662a6d3

Please sign in to comment.