Feature(react): Move settings to React

This commit is contained in:
Berkeley Martinez
2016-07-15 14:32:42 -07:00
parent 822eb541de
commit 2f98976de1
11 changed files with 474 additions and 188 deletions

View File

@ -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

View File

@ -0,0 +1,61 @@
import React, { PropTypes } from 'react';
import { Modal, Button } from 'react-bootstrap';
export default function DeleteModal({ isOpen }) {
return (
<div>
<Button
block={ true }
bsSize='lg'
bsStyle='danger'
className='btn-link-social'
>
Delete my Free Code Camp account
</Button>
<Modal
backdrop={ true }
show={ isOpen }
>
<Modal.Header>
<h3>You don't really want to delete your account, do you?</h3>
</Modal.Header>
<Modal.Body>
<p>
This will really delete all your data, including
all your progress and brownie points.
</p>
<p>
We won't be able to recover any of it for you later,
even if you change your mind.
</p>
<p>
If there's something we could do better, send
us an email instead and we'll do our best: &thinsp;
<a href='mailto:team@freecodecamp.com'>
team@freecodecamp.com
</a>.
</p>
</Modal.Body>
<Modal.Footer>
<Button
block={ true }
bsStyle='success'
>
Nevermind, I don't want to delete all of my progress
</Button>
<div className='spacer' />
<Button
block={ true }
bsStyle='danger'
>
I am 100% sure I want to delete my account and all of my progress
</Button>
</Modal.Footer>
</Modal>
</div>
);
}
DeleteModal.propTypes = {
isOpen: PropTypes.bool
};

View File

@ -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 (
<Button
block={ true }
bsSize='lg'
bsStyle='primary'
className='btn-link-social'
>
<FA name='envelope' />
Update my Email
</Button>
);
}
export default function EmailSettings({
email,
sendMonthlyEmail,
sendNotificationEmail,
sendQuincyEmail
}) {
if (!email) {
return (
<div>
<Row>
<p className='large-p text-center'>
You don't have an email id associated to this account.
</p>
</Row>
<Row>
<UpdateEmailButton />
</Row>
</div>
);
}
return (
<div>
<Row>
<p className='large-p text-center'>
<em>{ email }</em>
</p>
</Row>
<Row>
<UpdateEmailButton />
</Row>
<Row>
<Col xs={ 9 }>
<p className='large-p'>
Send me announcement emails
<br />
(we'll send you these every Thursday)
</p>
</Col>
<Col xs={ 3 }>
<Button
block={ true }
bsSize='lg'
bsStyle='primary'
className={
classnames('positive-20', { active: sendMonthlyEmail })
}
>
{ sendMonthlyEmail ? 'On' : 'Off' }
</Button>
</Col>
</Row>
<Row>
<Col xs={ 9 }>
<p className='large-p'>
Send me notification emails
<br />
(these will pertain to your account)
</p>
</Col>
<Col xs={ 3 }>
<Button
block={ true }
bsSize='lg'
bsStyle='primary'
className={
classnames('positive-20', { active: sendNotificationEmail })
}
>
{ sendNotificationEmail ? 'On' : 'Off' }
</Button>
</Col>
</Row>
<Row>
<Col xs={ 9 }>
<p className='large-p'>
Send me Quincy's weekly email
<br />
(with new articles every Tuesday)
</p>
</Col>
<Col xs={ 3 }>
<Button
block={ true }
bsSize='lg'
bsStyle='primary'
className={
classnames('positive-20', { active: sendQuincyEmail })
}
>
{ sendQuincyEmail ? 'On' : 'Off' }
</Button>
</Col>
</Row>
</div>
);
}
EmailSettings.propTypes = {
email: PropTypes.string,
sendMonthlyEmail: PropTypes.bool,
sendNotificationEmail: PropTypes.bool,
sendQuincyEmail: PropTypes.bool
};

View File

@ -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 (
<option
key={ tag }
value={ tag }
>
{ langs[tag] }
</option>
);
}), (
<option
disabled={ true }
key='more'
>
More to come...
</option>
)
];
export default function LangaugeSettings({ userLang }) {
const options = [(
<option
disabled={ true }
key='default'
selected={ userLang ? false : true }
>
Prefered Langauge
</option>
),
...langOptions
];
return (
<FormControl
className='btn btn-block btn-primary btn-link-social'
componentClass='select'
>
{ options }
</FormControl>
);
}
LangaugeSettings.propTypes = {
userLang: PropTypes.string
};

View File

@ -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 (
<Row>
<Col xs={ 9 }>
<p className='large-p'>
Make all of my solutions private
<br />
(this disables your certificates)
</p>
</Col>
<Col xs={ 3 }>
<Button
block={ true }
bsSize='lg'
bsStyle='primary'
className={ className }
>
{ isLocked ? 'On' : 'Off' }
</Button>
</Col>
</Row>
);
}
LockSettings.propTypes = {
isLocked: PropTypes.bool
};

View File

@ -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 (
<div>
<h1 className='text-center'>Settings for your Account</h1>
<h2 className='text-center'>Actions</h2>
<Row>
<Col xs={ 12 }>
<Button
block={ true }
bsSize='lg'
bsStyle='primary'
className='btn-link-social'
>
NightMode
</Button>
</Col>
</Row>
<Row>
<Col xs={ 12 }>
<SocialSettings
isGithubCool={ isGithubCool }
isLinkedIn={ isLinkedIn }
isTwitter={ isTwitter }
/>
</Col>
</Row>
<div className='spacer' />
<h2 className='text-center'>Account Settings</h2>
<Row>
<Col xs={ 12 }>
<Button
block={ true }
bsSize='lg'
bsStyle='primary'
className='btn-link-social'
href='/commit'
>
Edit my pledge
</Button>
</Col>
</Row>
<div className='spacer' />
<h2 className='text-center'>Privacy Settings</h2>
<Row>
<Col
md={ 6 }
mdOffset={ 3 }
sm={ 8 }
smOffset={ 2 }
xs={ 12 }
>
<LockedSettings isLocked={ isLocked } />
</Col>
</Row>
<div className='spacer' />
<h2 className='text-center'>Email Settings</h2>
<Row>
<Col
md={ 6 }
mdOffset={ 3 }
sm={ 8 }
smOffset={ 2 }
xs={ 12 }
>
<EmailSettings
email={ email }
sendMonthlyEmail={ sendMonthlyEmail }
sendNotificationEmail={ sendNotificationEmail }
sendQuincyEmail={ sendQuincyEmail }
/>
</Col>
</Row>
<div className='spacer' />
<h2 className='text-center'>Language Settigns</h2>
<Row>
<Col
md={ 6 }
mdOffset={ 3 }
sm={ 8 }
smOffset={ 2 }
xs={ 12 }
>
<LangaugeSettings />
</Col>
</Row>
<div className='spacer' />
<h2 className='text-center'>Danger Zone</h2>
<Row>
<Col
md={ 6 }
mdOffset={ 3 }
sm={ 8 }
smOffset={ 2 }
xs={ 12 }
>
<DeleteModal />
</Col>
</Row>
</div>
);
}
}

View File

@ -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 = [
<Button
block={ true }
bsSize='lg'
className='btn-link-social btn-github'
href='/link/github'
key='github'
>
<FA name='github' />
{ githubCopy }
</Button>
];
if (isGithubCool && !isTwitter) {
buttons.push((
<Button
block={ true }
bsSize='lg'
className='btn-link-social btn-twitter'
href='/link/twitter'
key='twitter'
>
<FA name='twitter' />
Add my LinkedIn to my portfolio
</Button>
));
}
if (isGithubCool && !isLinkedIn) {
buttons.push((
<Button
block={ true }
bsSize='lg'
className='btn-link-social btn-linkedin'
href='/link/linkedin'
key='linkedin'
>
<FA name='linked' />
Add my LinkedIn to my portfolio
</Button>
));
}
return (<div>{ buttons }</div>);
}
SocialSettings.propTypes = {
isGithubCool: PropTypes.bool,
isTwitter: PropTypes.bool,
isLinkedIn: PropTypes.bool
};

View File

@ -0,0 +1,6 @@
import Settings from './components/Settings.jsx';
export default {
path: 'settings',
component: Settings
};

View File

@ -14,7 +14,8 @@ const log = debug('fcc:react-server');
const routes = [
'/challenges',
'/challenges/*',
'/map'
'/map',
'/settings'
];
const devRoutes = [];

View File

@ -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');
// }
// }
};

View File

@ -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: &thinsp;
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