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