From b1e9a172a2bb49ccaf4fe939e5002a72b317fe1b Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Fri, 15 Dec 2017 13:53:32 -0800 Subject: [PATCH] Feat: anon navbar (#16189) * chore(React): %s/react-pure-render/React.PureComponent/gc * fix(Settings): Should redirect to signup when unauthen * feat(Development): Use SES for mail if defined * feat(Nav): Show anon navbar when logged in * fix(server/datasources): Make sure mailhog works if no ses keys are found LB will use both mail settings if using both local and dev * fix(Nav): Use text instead of icons * fix(Nav): Make donate page open in new tab --- client/epics/hard-go-to-epic.js | 16 ++++--- common/app/Map/Block.jsx | 3 +- common/app/Map/Challenge.jsx | 3 +- common/app/Map/Map.jsx | 3 +- common/app/Map/Super-Block.jsx | 3 +- common/app/Nav/Nav.jsx | 27 +++-------- common/app/Nav/Sign-Up.jsx | 25 ++--------- common/app/Nav/links.json | 3 +- common/app/Nav/nav.less | 23 +--------- common/app/routes/Challenges/Bug-Modal.jsx | 3 +- .../Challenges/Code-Mirror-Skeleton.jsx | 3 +- common/app/routes/Challenges/Side-Panel.jsx | 3 +- .../Challenges/views/project/Project.jsx | 3 +- .../Challenges/views/project/Tool-Panel.jsx | 3 +- common/app/routes/Settings/Settings.jsx | 12 ++++- package-lock.json | 11 ++--- package.json | 3 +- server/datasources.development.js | 45 ++++++++++++------- ...ces.local.js => datasources.production.js} | 0 server/views/partials/navbar.jade | 5 ++- server/views/partials/react-stylesheets.jade | 2 +- 21 files changed, 78 insertions(+), 121 deletions(-) rename server/{datasources.local.js => datasources.production.js} (100%) diff --git a/client/epics/hard-go-to-epic.js b/client/epics/hard-go-to-epic.js index f485fa1276..895ee49b70 100644 --- a/client/epics/hard-go-to-epic.js +++ b/client/epics/hard-go-to-epic.js @@ -1,10 +1,14 @@ -import { types } from '../../common/app/redux'; +import _ from 'lodash'; import { ofType } from 'redux-epic'; -export default function hardGoToSaga(actions, _, { history }) { +import { types } from '../../common/app/redux'; + +export default function hardGoToSaga(actions, store, { location }) { return actions::ofType(types.hardGoTo) - .map(({ payload = '/settings' }) => { - history.pushState(history.state, null, payload); - return null; - }); + .pluck('payload') + .filter(_.isString) + .do((payload = '/') => { + location.pathname = payload; + }) + .ignoreElements(); } diff --git a/common/app/Map/Block.jsx b/common/app/Map/Block.jsx index b71d70ce23..d7df4b48d4 100644 --- a/common/app/Map/Block.jsx +++ b/common/app/Map/Block.jsx @@ -1,9 +1,8 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import FA from 'react-fontawesome'; -import PureComponent from 'react-pure-render/component'; import { Panel } from 'react-bootstrap'; import ns from './ns.json'; diff --git a/common/app/Map/Challenge.jsx b/common/app/Map/Challenge.jsx index 8e74dcf1b8..c6d94b5091 100644 --- a/common/app/Map/Challenge.jsx +++ b/common/app/Map/Challenge.jsx @@ -1,8 +1,7 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import PureComponent from 'react-pure-render/component'; import classnames from 'classnames'; import debug from 'debug'; diff --git a/common/app/Map/Map.jsx b/common/app/Map/Map.jsx index 83a99c6043..056bab9926 100644 --- a/common/app/Map/Map.jsx +++ b/common/app/Map/Map.jsx @@ -1,7 +1,6 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import PureComponent from 'react-pure-render/component'; import { Col, Row } from 'react-bootstrap'; import ns from './ns.json'; diff --git a/common/app/Map/Super-Block.jsx b/common/app/Map/Super-Block.jsx index 8a82f1a7f5..63285fb0e6 100644 --- a/common/app/Map/Super-Block.jsx +++ b/common/app/Map/Super-Block.jsx @@ -1,8 +1,7 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import PureComponent from 'react-pure-render/component'; import FA from 'react-fontawesome'; import { Panel } from 'react-bootstrap'; diff --git a/common/app/Nav/Nav.jsx b/common/app/Nav/Nav.jsx index 7502a9b1f5..b332c279a2 100644 --- a/common/app/Nav/Nav.jsx +++ b/common/app/Nav/Nav.jsx @@ -27,24 +27,18 @@ import { dropdownSelector } from './redux'; -import { - userSelector, - isSignedInSelector, - signInLoadingSelector -} from '../redux'; +import { isSignedInSelector, signInLoadingSelector } from '../redux'; import { panesSelector } from '../Panes/redux'; const fCClogo = 'https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg'; const mapStateToProps = createSelector( - userSelector, isSignedInSelector, dropdownSelector, signInLoadingSelector, panesSelector, ( - { username, picture, points }, isSignedIn, isDropdownOpen, showLoading, @@ -59,10 +53,7 @@ const mapStateToProps = createSelector( }, {}), isDropdownOpen, isSignedIn, - picture, - points, - showLoading, - username + showLoading }; } ); @@ -111,13 +102,11 @@ const propTypes = { clickOnMap: PropTypes.func.isRequired, closeDropdown: PropTypes.func.isRequired, isDropdownOpen: PropTypes.bool, + isSignedIn: PropTypes.bool, openDropdown: PropTypes.func.isRequired, panes: PropTypes.array, - picture: PropTypes.string, - points: PropTypes.number, showLoading: PropTypes.bool, - signedIn: PropTypes.bool, - username: PropTypes.string + signedIn: PropTypes.bool }; export class FCCNav extends React.Component { @@ -178,11 +167,9 @@ export class FCCNav extends React.Component { render() { const { panes, + isSignedIn, clickOnLogo, clickOnMap, - username, - points, - picture, showLoading } = this.props; @@ -236,10 +223,8 @@ export class FCCNav extends React.Component { ) } diff --git a/common/app/Nav/Sign-Up.jsx b/common/app/Nav/Sign-Up.jsx index 63004fe16a..ff7b8561ac 100644 --- a/common/app/Nav/Sign-Up.jsx +++ b/common/app/Nav/Sign-Up.jsx @@ -5,26 +5,16 @@ import { NavItem } from 'react-bootstrap'; import { Link } from '../Router'; import { onRouteSettings } from '../routes/Settings/redux'; -// this is separated out to prevent react bootstrap's -// NavBar from injecting unknown props to the li component - const propTypes = { - picture: PropTypes.string, - points: PropTypes.number, showLoading: PropTypes.bool, - username: PropTypes.string + showSignUp: PropTypes.bool }; -export default function SignUpButton({ - picture, - points, - showLoading, - username -}) { +export default function SignUpButton({ showLoading, showSignUp }) { if (showLoading) { return null; } - if (!username) { + if (showSignUp) { return ( - { username } - [ { points || 1 } ] - - - + My Profile ); diff --git a/common/app/Nav/links.json b/common/app/Nav/links.json index a3f0b6f16f..bfebcc7ad9 100644 --- a/common/app/Nav/links.json +++ b/common/app/Nav/links.json @@ -37,6 +37,7 @@ }, { "content": "Donate", - "link": "https://www.freecodecamp.org/donate" + "link": "https://www.freecodecamp.org/donate", + "target": "_blank" } ] diff --git a/common/app/Nav/nav.less b/common/app/Nav/nav.less index 358cf3dda1..d9284fd37c 100644 --- a/common/app/Nav/nav.less +++ b/common/app/Nav/nav.less @@ -77,31 +77,10 @@ li.nav-avatar { > a { margin: 0; - padding: 0 @navbar-padding-horizontal 0 @navbar-padding-horizontal; + padding: 7.5px @navbar-padding-horizontal 7.5px @navbar-padding-horizontal; } } } - -.nav-username { - padding-right: @navbar-padding-horizontal; -} - -.nav-points { - @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { - padding: @navbar-padding-vertical 0 @navbar-padding-vertical 0; - } - @media (min-width: @screen-md-min) { - padding: @navbar-padding-vertical @navbar-padding-horizontal @navbar-padding-vertical 0; - } -} - -.nav-picture { - margin-top: @navbar-logo-padding; - margin-bottom: @navbar-logo-padding; - height: @navbar-logo-height; - width: @navbar-logo-height; -} - .navbar-nav a { color: @body-bg; margin-top: -5px; diff --git a/common/app/routes/Challenges/Bug-Modal.jsx b/common/app/routes/Challenges/Bug-Modal.jsx index 7366957d9d..3b2a48ae3f 100644 --- a/common/app/routes/Challenges/Bug-Modal.jsx +++ b/common/app/routes/Challenges/Bug-Modal.jsx @@ -1,8 +1,7 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { Button, Modal } from 'react-bootstrap'; -import PureComponent from 'react-pure-render/component'; import ns from './ns.json'; import { diff --git a/common/app/routes/Challenges/Code-Mirror-Skeleton.jsx b/common/app/routes/Challenges/Code-Mirror-Skeleton.jsx index 0b07b8a32c..02567457ca 100644 --- a/common/app/routes/Challenges/Code-Mirror-Skeleton.jsx +++ b/common/app/routes/Challenges/Code-Mirror-Skeleton.jsx @@ -1,6 +1,5 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import PureComponent from 'react-pure-render/component'; import { Grid, Col, Row } from 'react-bootstrap'; import ns from './ns.json'; diff --git a/common/app/routes/Challenges/Side-Panel.jsx b/common/app/routes/Challenges/Side-Panel.jsx index a40075e7c6..14c1027f4f 100644 --- a/common/app/routes/Challenges/Side-Panel.jsx +++ b/common/app/routes/Challenges/Side-Panel.jsx @@ -1,9 +1,8 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import ReactDom from 'react-dom'; import { createSelector } from 'reselect'; import { connect } from 'react-redux'; -import PureComponent from 'react-pure-render/component'; import ns from './ns.json'; diff --git a/common/app/routes/Challenges/views/project/Project.jsx b/common/app/routes/Challenges/views/project/Project.jsx index dcbe854099..53b6e6234a 100644 --- a/common/app/routes/Challenges/views/project/Project.jsx +++ b/common/app/routes/Challenges/views/project/Project.jsx @@ -1,8 +1,7 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { createSelector } from 'reselect'; import { connect } from 'react-redux'; -import PureComponent from 'react-pure-render/component'; import { Col, Image } from 'react-bootstrap'; import SidePanel from './Side-Panel.jsx'; diff --git a/common/app/routes/Challenges/views/project/Tool-Panel.jsx b/common/app/routes/Challenges/views/project/Tool-Panel.jsx index 9335c29ce4..96c3a34a76 100644 --- a/common/app/routes/Challenges/views/project/Tool-Panel.jsx +++ b/common/app/routes/Challenges/views/project/Tool-Panel.jsx @@ -1,6 +1,5 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import PureComponent from 'react-pure-render/component'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; diff --git a/common/app/routes/Settings/Settings.jsx b/common/app/routes/Settings/Settings.jsx index 8d4c94c24d..40426b4b86 100644 --- a/common/app/routes/Settings/Settings.jsx +++ b/common/app/routes/Settings/Settings.jsx @@ -21,7 +21,8 @@ import { signInLoadingSelector, userSelector, - themeSelector + themeSelector, + hardGoTo } from '../../redux'; import ChildContainer from '../../Child-Container.jsx'; @@ -64,6 +65,7 @@ const mapStateToProps = createSelector( ); const mapDispatchToProps = { + hardGoTo, toggleIsAvailableForHire: () => toggleUserFlag('isAvailableForHire'), toggleIsLocked: () => toggleUserFlag('isLocked'), toggleMonthlyEmail: () => toggleUserFlag('sendMonthlyEmail'), @@ -77,6 +79,7 @@ const propTypes = { children: PropTypes.element, currentTheme: PropTypes.string, email: PropTypes.string, + hardGoTo: PropTypes.func.isRequired, initialLang: PropTypes.string, isAvailableForHire: PropTypes.bool, isGithubCool: PropTypes.bool, @@ -115,6 +118,11 @@ export class Settings extends React.Component { componentWillMount() { this.props.updateTitle('Settings'); } + componentWillReceiveProps({ username, showLoading, hardGoTo }) { + if (!username && !showLoading) { + hardGoTo('/signup'); + } + } render() { const { @@ -138,7 +146,7 @@ export class Settings extends React.Component { toggleQuincyEmail, username } = this.props; - if (!username && !showLoading) { + if (!username && showLoading) { return ; } if (showUpdateEmailView) { diff --git a/package-lock.json b/package-lock.json index b574a3bfb4..56b6fa6db7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5391,9 +5391,9 @@ } }, "font-awesome": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.5.0.tgz", - "integrity": "sha1-Hp18z31jvb5XAA4Y1RiMslV+cPg=" + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", + "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" }, "for-each": { "version": "0.3.2", @@ -12781,11 +12781,6 @@ "warning": "3.0.0" } }, - "react-pure-render": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/react-pure-render/-/react-pure-render-1.0.2.tgz", - "integrity": "sha1-nYqSjH8sN1E8LQZOV7Pjw1bp+rs=" - }, "react-redux": { "version": "4.4.8", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-4.4.8.tgz", diff --git a/package.json b/package.json index 509793448c..22a5b87379 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "express-state": "^1.2.0", "express-validator": "^3.0.0", "fetchr": "~0.5.12", - "font-awesome": "~4.5.0", + "font-awesome": "^4.7.0", "frameguard": "^3.0.0", "googleapis": "16.1.0", "helmet": "^3.1.0", @@ -115,7 +115,6 @@ "react-motion": "~0.4.2", "react-no-ssr": "^1.0.1", "react-notification": "git+https://github.com/BerkeleyTrue/react-notification.git#freecodecamp", - "react-pure-render": "^1.0.2", "react-redux": "^4.0.6", "react-test-renderer": "^15.6.2", "react-youtube": "^7.0.0", diff --git a/server/datasources.development.js b/server/datasources.development.js index 90ee290f06..2239ded02c 100644 --- a/server/datasources.development.js +++ b/server/datasources.development.js @@ -1,18 +1,29 @@ -module.exports = { - mail: { - connector: 'mail', - transport: { - type: 'smtp', - host: 'localhost', - secure: false, - port: 1025, - tls: { - rejectUnauthorized: false - } - }, - auth: { - user: 'test', - pass: 'test' - } - } +const debug = require('debug')('fcc:server:datasources'); +const dsLocal = require('./datasources.production.js'); + +const ds = { + ...dsLocal }; +// use [MailHog](https://github.com/mailhog/MailHog) if no SES keys are found +if (!process.env.SES_ID) { + ds.mail = { + connector: 'mail', + transport: { + type: 'smtp', + host: 'localhost', + secure: false, + port: 1025, + tls: { + rejectUnauthorized: false + } + }, + auth: { + user: 'test', + pass: 'test' + } + }; + debug(`using MailHog server on port ${ds.mail.transport.port}`); +} else { + debug('using AWS SES to deliver emails'); +} +module.exports = ds; diff --git a/server/datasources.local.js b/server/datasources.production.js similarity index 100% rename from server/datasources.local.js rename to server/datasources.production.js diff --git a/server/views/partials/navbar.jade b/server/views/partials/navbar.jade index e6c4869be8..725f71dae9 100644 --- a/server/views/partials/navbar.jade +++ b/server/views/partials/navbar.jade @@ -1,4 +1,5 @@ nav.navbar.navbar-default.navbar-static-top.nav-height + .navbar-header button.hamburger.navbar-toggle(type='button', data-toggle='collapse', data-target='.navbar-collapse') .col-xs-12 @@ -25,10 +26,10 @@ nav.navbar.navbar-default.navbar-static-top.nav-height li a(href='/map') Map li - a(href='https://www.freecodecamp.org/donate') Donate + a(href='https://www.freecodecamp.org/donate', target='_blank') Donate if !user li - a(href='/signin') Sign In + a(href='/signin') Sign Up else li.nav-avatar a(href='/settings') diff --git a/server/views/partials/react-stylesheets.jade b/server/views/partials/react-stylesheets.jade index 51d119bb73..7c80fa6896 100644 --- a/server/views/partials/react-stylesheets.jade +++ b/server/views/partials/react-stylesheets.jade @@ -1,6 +1,6 @@ link(rel='stylesheet', type='text/css' href='/css/lato.css') link(rel='stylesheet', type='text/css' href='/css/ubuntu.css') -link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css') +link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css') link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.30.0/codemirror.min.css') link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.30.0/addon/lint/lint.min.css') link(rel='stylesheet', href=rev('/css', 'main.css'))