From 2f98976de1d3fbc36b312629ceca0d8b42cc65c6 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Fri, 15 Jul 2016 14:32:42 -0700 Subject: [PATCH] Feature(react): Move settings to React --- common/app/routes/index.js | 2 + .../settings/components/Delete-Modal.jsx | 61 ++++++++ .../settings/components/Email-Setting.jsx | 122 +++++++++++++++ .../settings/components/Language-Settings.jsx | 49 ++++++ .../settings/components/Locked-Settings.jsx | 35 +++++ .../routes/settings/components/Settings.jsx | 135 +++++++++++++++++ .../settings/components/Social-Settings.jsx | 60 ++++++++ common/app/routes/settings/index.js | 6 + server/boot/react.js | 3 +- server/boot/user.js | 47 +----- server/views/account/settings.jade | 142 ------------------ 11 files changed, 474 insertions(+), 188 deletions(-) create mode 100644 common/app/routes/settings/components/Delete-Modal.jsx create mode 100644 common/app/routes/settings/components/Email-Setting.jsx create mode 100644 common/app/routes/settings/components/Language-Settings.jsx create mode 100644 common/app/routes/settings/components/Locked-Settings.jsx create mode 100644 common/app/routes/settings/components/Settings.jsx create mode 100644 common/app/routes/settings/components/Social-Settings.jsx create mode 100644 common/app/routes/settings/index.js delete mode 100644 server/views/account/settings.jade diff --git a/common/app/routes/index.js b/common/app/routes/index.js index 36a4bd2d55..3605be8d64 100644 --- a/common/app/routes/index.js +++ b/common/app/routes/index.js @@ -1,6 +1,7 @@ import { modernChallenges, map, challenges } from './challenges'; import NotFound from '../components/NotFound/index.jsx'; import { addLang } from '../utils/lang'; +import settings from './settings'; export default { path: '/:lang', @@ -15,6 +16,7 @@ export default { challenges, modernChallenges, map, + settings, { path: '*', component: NotFound diff --git a/common/app/routes/settings/components/Delete-Modal.jsx b/common/app/routes/settings/components/Delete-Modal.jsx new file mode 100644 index 0000000000..d18a50c8fb --- /dev/null +++ b/common/app/routes/settings/components/Delete-Modal.jsx @@ -0,0 +1,61 @@ +import React, { PropTypes } from 'react'; +import { Modal, Button } from 'react-bootstrap'; + +export default function DeleteModal({ isOpen }) { + return ( +
+ + + +

You don't really want to delete your account, do you?

+
+ +

+ This will really delete all your data, including + all your progress and brownie points. +

+

+ We won't be able to recover any of it for you later, + even if you change your mind. +

+

+ If there's something we could do better, send + us an email instead and we'll do our best:   + + team@freecodecamp.com + . +

+
+ + +
+ + + +
+ ); +} + +DeleteModal.propTypes = { + isOpen: PropTypes.bool +}; diff --git a/common/app/routes/settings/components/Email-Setting.jsx b/common/app/routes/settings/components/Email-Setting.jsx new file mode 100644 index 0000000000..418e86e145 --- /dev/null +++ b/common/app/routes/settings/components/Email-Setting.jsx @@ -0,0 +1,122 @@ +import React, { PropTypes } from 'react'; +import { Button, Row, Col } from 'react-bootstrap'; +import FA from 'react-fontawesome'; +import classnames from 'classnames'; + +export function UpdateEmailButton() { + return ( + + ); +} + +export default function EmailSettings({ + email, + sendMonthlyEmail, + sendNotificationEmail, + sendQuincyEmail +}) { + if (!email) { + return ( +
+ +

+ You don't have an email id associated to this account. +

+
+ + + +
+ ); + } + return ( +
+ +

+ { email } +

+
+ + + + + +

+ Send me announcement emails +
+ (we'll send you these every Thursday) +

+ + + + +
+ + +

+ Send me notification emails +
+ (these will pertain to your account) +

+ + + + +
+ + +

+ Send me Quincy's weekly email +
+ (with new articles every Tuesday) +

+ + + + +
+
+ ); +} + +EmailSettings.propTypes = { + email: PropTypes.string, + sendMonthlyEmail: PropTypes.bool, + sendNotificationEmail: PropTypes.bool, + sendQuincyEmail: PropTypes.bool +}; diff --git a/common/app/routes/settings/components/Language-Settings.jsx b/common/app/routes/settings/components/Language-Settings.jsx new file mode 100644 index 0000000000..6f907803ff --- /dev/null +++ b/common/app/routes/settings/components/Language-Settings.jsx @@ -0,0 +1,49 @@ +import React, { PropTypes } from 'react'; +import { FormControl } from 'react-bootstrap'; +import langs from '../../../../utils/supported-languages'; + +const langOptions = [ + ...Object.keys(langs).map(tag => { + return ( + + ); + }), ( + + ) +]; + +export default function LangaugeSettings({ userLang }) { + const options = [( + + ), + ...langOptions + ]; + return ( + + { options } + + ); +} + +LangaugeSettings.propTypes = { + userLang: PropTypes.string +}; diff --git a/common/app/routes/settings/components/Locked-Settings.jsx b/common/app/routes/settings/components/Locked-Settings.jsx new file mode 100644 index 0000000000..e9dbee516f --- /dev/null +++ b/common/app/routes/settings/components/Locked-Settings.jsx @@ -0,0 +1,35 @@ +import React, { PropTypes } from 'react'; +import { Button, Row, Col } from 'react-bootstrap'; +import classnames from 'classnames'; + +export default function LockSettings({ isLocked }) { + const className = classnames({ + 'positive-20': true, + active: isLocked + }); + return ( + + +

+ Make all of my solutions private +
+ (this disables your certificates) +

+ + + + +
+ ); +} + +LockSettings.propTypes = { + isLocked: PropTypes.bool +}; diff --git a/common/app/routes/settings/components/Settings.jsx b/common/app/routes/settings/components/Settings.jsx new file mode 100644 index 0000000000..422e2f4de2 --- /dev/null +++ b/common/app/routes/settings/components/Settings.jsx @@ -0,0 +1,135 @@ +import React, { PropTypes } from 'react'; +import { Button, Row, Col } from 'react-bootstrap'; + +import LockedSettings from './Locked-Settings.jsx'; +import SocialSettings from './Social-Settings.jsx'; +import EmailSettings from './Email-Setting.jsx'; +import LangaugeSettings from './Language-Settings.jsx'; +import DeleteModal from './Delete-Modal.jsx'; + +export default class Settings extends React.Component { + static displayName = 'Settings'; + static propTypes = { + isLocked: PropTypes.bool, + isGithubCool: PropTypes.bool, + isTwitter: PropTypes.bool, + isLinkedIn: PropTypes.bool, + email: PropTypes.string, + sendMonthlyEmail: PropTypes.bool, + sendNotificationEmail: PropTypes.bool, + sendQuincyEmail: PropTypes.bool + }; + + render() { + const { + isLocked, + isGithubCool, + isTwitter, + isLinkedIn, + email, + sendMonthlyEmail, + sendNotificationEmail, + sendQuincyEmail + } = this.props; + return ( +
+

Settings for your Account

+

Actions

+ + + + + + + + + + +
+

Account Settings

+ + + + + +
+

Privacy Settings

+ + + + + +
+

Email Settings

+ + + + + +
+

Language Settigns

+ + + + + +
+

Danger Zone

+ + + + + +
+ ); + } +} + diff --git a/common/app/routes/settings/components/Social-Settings.jsx b/common/app/routes/settings/components/Social-Settings.jsx new file mode 100644 index 0000000000..53a9414d59 --- /dev/null +++ b/common/app/routes/settings/components/Social-Settings.jsx @@ -0,0 +1,60 @@ +import React, { PropTypes } from 'react'; +import { Button } from 'react-bootstrap'; +import FA from 'react-fontawesome'; + +export default function SocialSettings({ + isGithubCool, + isTwitter, + isLinkedIn +}) { + const githubCopy = isGithubCool ? + 'Update my portfolio from GitHub' : + 'Link my GitHub to unlock my portfolio'; + const buttons = [ + + ]; + if (isGithubCool && !isTwitter) { + buttons.push(( + + )); + } + if (isGithubCool && !isLinkedIn) { + buttons.push(( + + )); + } + return (
{ buttons }
); +} + +SocialSettings.propTypes = { + isGithubCool: PropTypes.bool, + isTwitter: PropTypes.bool, + isLinkedIn: PropTypes.bool +}; diff --git a/common/app/routes/settings/index.js b/common/app/routes/settings/index.js new file mode 100644 index 0000000000..d95cea8dc8 --- /dev/null +++ b/common/app/routes/settings/index.js @@ -0,0 +1,6 @@ +import Settings from './components/Settings.jsx'; + +export default { + path: 'settings', + component: Settings +}; diff --git a/server/boot/react.js b/server/boot/react.js index 41de457b72..216eab66e4 100644 --- a/server/boot/react.js +++ b/server/boot/react.js @@ -14,7 +14,8 @@ const log = debug('fcc:react-server'); const routes = [ '/challenges', '/challenges/*', - '/map' + '/map', + '/settings' ]; const devRoutes = []; diff --git a/server/boot/user.js b/server/boot/user.js index 19c7d8cc76..b8c179e7fe 100644 --- a/server/boot/user.js +++ b/server/boot/user.js @@ -14,8 +14,7 @@ import certTypes from '../utils/certTypes.json'; import { ifNoUser401, - ifNoUserRedirectTo, - flashIfNotVerified + ifNoUserRedirectTo } from '../utils/middleware'; import { observeQuery } from '../utils/rx'; import { @@ -186,12 +185,6 @@ module.exports = function(app) { sendNonUserToMap, getAccount ); - router.get( - '/settings', - sendNonUserToMap, - flashIfNotVerified, - getSettings - ); // Ensure these are the last routes! api.get( @@ -217,7 +210,7 @@ module.exports = function(app) { router.get('/:username', returnUser); app.use('/:lang', router); - app.use(router); + app.use(api); function getSignin(req, res) { if (req.user) { @@ -275,12 +268,6 @@ module.exports = function(app) { return res.redirect('/' + username); } - function getSettings(req, res) { - res.render('account/settings', { - title: 'Settings' - }); - } - function returnUser(req, res, next) { const username = req.params.username.toLowerCase(); const { user } = req; @@ -579,34 +566,4 @@ module.exports = function(app) { return res.render('account/forgot'); }); } - - // function vote1(req, res, next) { - // if (req.user) { - // req.user.tshirtVote = 1; - // req.user.save(function(err) { - // if (err) { return next(err); } - // - // req.flash('success', { msg: 'Thanks for voting!' }); - // return res.redirect('/map'); - // }); - // } else { - // req.flash('error', { msg: 'You must be signed in to vote.' }); - // res.redirect('/map'); - // } - // } - // - // function vote2(req, res, next) { - // if (req.user) { - // req.user.tshirtVote = 2; - // req.user.save(function(err) { - // if (err) { return next(err); } - // - // req.flash('success', { msg: 'Thanks for voting!' }); - // return res.redirect('/map'); - // }); - // } else { - // req.flash('error', {msg: 'You must be signed in to vote.'}); - // res.redirect('/map'); - // } - // } }; diff --git a/server/views/account/settings.jade b/server/views/account/settings.jade deleted file mode 100644 index fee9250cae..0000000000 --- a/server/views/account/settings.jade +++ /dev/null @@ -1,142 +0,0 @@ -extends ../layout -block content - h1.text-center Settings for your Account - hr - h2.text-center Actions - .row - .col-xs-12 - a#night-mode.btn.btn-lg.btn-block.btn-primary.btn-link-social Night Mode - .row - .col-xs-12 - if (!user.isGithubCool) - a.btn.btn-lg.btn-block.btn-github.btn-link-social(href='/link/github') - i.fa.fa-github - | Link my GitHub to unlock my portfolio - else - a.btn.btn-lg.btn-block.btn-github.btn-link-social(href='/link/github') - i.fa.fa-github - | Update my portfolio from GitHub - if (!user.twitter) - a.btn.btn-lg.btn-block.btn-twitter.btn-link-social(href='/link/twitter') - i.fa.fa-twitter - | Add my Twitter to my portfolio - if (!user.linkedin) - a.btn.btn-lg.btn-block.btn-linkedin.btn-link-social(href='/link/linkedin') - i.fa.fa-linkedin - | Add my LinkedIn to my portfolio - .spacer - h2.text-center Account Settings - .row - .col-xs-12 - a.btn.btn-lg.btn-block.btn-primary.btn-link-social(href='/commit') - | Edit my pledge - .spacer - h2.text-center Privacy Settings - .row - .col-xs-12.col-sm-8.col-sm-offset-2.col-md-6.col-md-offset-3 - .row - .col-xs-9 - p.large-p Make all of my solutions private - br - | (this disables your certificates) - if (user.isLocked) - .col-xs-3 - a.btn.btn-lg.btn-primary.btn-block.active.positive-20(href='/toggle-lockdown-mode') On - else - .col-xs-3 - a.btn.btn-lg.btn-primary.btn-block.positive-20(href='/toggle-lockdown-mode') Off - - .spacer - h2.text-center Email settings - if(user.email) - .row - .col-xs-12 - p.large-p.text-center - em #{user.email} - .col-xs-12 - a.btn.btn-lg.btn-block.btn-primary.btn-link-social(href='/update-email') - i.fa.fa-envelope - | Update my Email - .row - .col-xs-12.col-sm-8.col-sm-offset-2.col-md-6.col-md-offset-3 - .row - .col-xs-9 - p.large-p Send me announcement emails - br - | (we'll send you these every Thursday) - if (user.sendMonthlyEmail) - .col-xs-3 - a.btn.btn-lg.btn-primary.btn-block.active.positive-20(href='/toggle-announcement-email-mode') On - else - .col-xs-3 - a.btn.btn-lg.btn-primary.btn-block.positive-20(href='/toggle-announcement-email-mode') Off - - .row - .col-xs-9 - p.large-p Send me notification emails - br - | (these will pertain to your account) - if (user.sendNotificationEmail) - .col-xs-3 - a.btn.btn-lg.btn-primary.btn-block.active.positive-20(href='/toggle-notification-email-mode') On - else - .col-xs-3 - a.btn.btn-lg.btn-primary.btn-block.positive-20(href='/toggle-notification-email-mode') Off - - .row - .col-xs-9 - p.large-p Send me Quincy's weekly email - br - | (with new articles every Tuesday) - if (user.sendQuincyEmail) - .col-xs-3 - a.btn.btn-lg.btn-primary.btn-block.active.positive-20(href='/toggle-quincy-email-mode') On - else - .col-xs-3 - a.btn.btn-lg.btn-primary.btn-block.positive-20(href='/toggle-quincy-email-mode') Off - else - .row - .col-xs-12 - p.large-p.text-center - | You don't have an email id associated to this account. - .col-xs-12 - a.btn.btn-lg.btn-block.btn-primary.btn-link-social(href='/update-email') - i.fa.fa-envelope - | Update my Email - if supportedLanguages - .col-xs-12 - select#lang-select.form-control.btn.btn-lg.btn-block.btn-primary.btn-link-social(name='langTag') - option(disabled selected=languageTag ? false : true) Prefered Language - for languageDisplay, lang in supportedLanguages - option(value=lang selected=lang === languageTag ? 'selected' : false)= languageDisplay - option(disabled) More to come... - .spacer - h2.text-center Danger Zone - .row - .col-xs-12 - a.btn.btn-lg.btn-block.btn-danger.btn-link-social.confirm-deletion - | Delete my Free Code Camp account - script. - $('.confirm-deletion').on("click", function () { - $('#modal-dialog').modal('show'); - }); - #modal-dialog.modal.animated.wobble - .modal-dialog - .modal-content - .modal-header - a.close(href='#', data-dismiss='modal', aria-hidden='true') × - h3 You don't really want to delete your account, do you? - .modal-body - p This will really delete all your data, including all your progress and brownie points. - p We won't be able to recover any of it for you later, even if you change your mind. - p If there's something we could do better, send us an email instead and we'll do our best:   - a(href="mailto:team@freecodecamp.com") team@freecodecamp.com - | . - .modal-footer - a.btn.btn-success.btn-block(href='#', data-dismiss='modal', aria-hidden='true') - | Nevermind, I don't want to delete all of my progress - .spacer - form(action='/account/delete', method='POST') - input(type='hidden', name='_csrf', value=_csrf) - button.btn.btn-danger.btn-block(type='submit') - | I am 100% sure I want to delete my account and all of my progress