Merge remote-tracking branch 'origin/staging' into staging

This commit is contained in:
benmcmahon100
2015-08-05 15:39:18 +01:00
13 changed files with 86 additions and 527 deletions

View File

@ -1,14 +1,16 @@
var debug = require('debug')('freecc:models:userIdent'); import debugFactory from 'debug';
var defaultProfileImage = const debug = debugFactory('freecc:models:userIdent');
require('../utils/constantStrings.json').defaultProfileImage;
const { defaultProfileImage } = require('../utils/constantStrings.json');
function getFirstImageFromProfile(profile) { function getFirstImageFromProfile(profile) {
return profile && profile.photos && profile.photos[0] ? return profile && profile.photos && profile.photos[0] ?
profile.photos[0].value : profile.photos[0].value :
null; null;
} }
module.exports = function(UserIdent) {
export default function(UserIdent) {
UserIdent.observe('before save', function(ctx, next) { UserIdent.observe('before save', function(ctx, next) {
var userIdent = ctx.currentInstance || ctx.instance; var userIdent = ctx.currentInstance || ctx.instance;
if (!userIdent) { if (!userIdent) {
@ -16,13 +18,14 @@ module.exports = function(UserIdent) {
return next(); return next();
} }
userIdent.user(function(err, user) { userIdent.user(function(err, user) {
let userChanged = false;
if (err) { return next(err); } if (err) { return next(err); }
if (!user) { if (!user) {
debug('no user attached to identity!'); debug('no user attached to identity!');
return next(); return next();
} }
var picture = getFirstImageFromProfile(userIdent.profile); const picture = getFirstImageFromProfile(userIdent.profile);
debug('picture', picture, user.picture); debug('picture', picture, user.picture);
// check if picture was found // check if picture was found
@ -34,15 +37,35 @@ module.exports = function(UserIdent) {
(!user.picture || user.picture === defaultProfileImage) (!user.picture || user.picture === defaultProfileImage)
) { ) {
debug('setting user picture'); debug('setting user picture');
user.picture = userIdent.profile.photos[0].value; user.picture = picture;
userChanged = true;
}
// if user signed in with github
// and user is not github cool
// or username is different from github username
// then make them github cool
// and set their username from their github profile.
if (
userIdent.provider === 'github-login' &&
(!user.isGithubCool ||
user.username !== userIdent.provider.username.toLowerCase())
) {
debug("user isn't github cool or username from github is different");
user.isGithubCool = true;
user.username = userIdent.profile.username.toLowerCase();
userChanged = true;
}
if (userChanged) {
return user.save(function(err) { return user.save(function(err) {
if (err) { return next(err); } if (err) { return next(err); }
next(); next();
}); });
} }
debug('exiting after user identity before save');
debug('exiting after user ident');
next(); next();
}); });
}); });
}; }

View File

@ -161,9 +161,9 @@ module.exports = function(User) {
if (!username) { if (!username) {
// Zalgo!! // Zalgo!!
return nextTick(() => { return nextTick(() => {
cb( cb(new TypeError(
new TypeError('FCC: username should be a string but got %s', username) `username should be a string but got ${ username }`
); ));
}); });
} }
User.findOne({ where: { username } }, (err, user) => { User.findOne({ where: { username } }, (err, user) => {
@ -171,7 +171,7 @@ module.exports = function(User) {
return cb(err); return cb(err);
} }
if (!user || user.username !== username) { if (!user || user.username !== username) {
return cb(new Error('FCC: no user found for %s', username)); return cb(new Error(`no user found for ${ username }`));
} }
const aboutUser = getAboutProfile(user); const aboutUser = getAboutProfile(user);
return cb(null, aboutUser); return cb(null, aboutUser);

View File

@ -19,28 +19,14 @@
"password": { "password": {
"type": "string" "type": "string"
}, },
"facebook": {
"type": "string"
},
"twitter": {
"type": "string"
},
"google": {
"type": "string"
},
"github": {
"type": "string"
},
"linkedin": {
"type": "string"
},
"tokens": {
"type": "array"
},
"progressTimestamps": { "progressTimestamps": {
"type": "array", "type": "array",
"default": [] "default": []
}, },
"isGithubCool": {
"type": "boolean",
"default": false
},
"username": { "username": {
"type": "string", "type": "string",
"lowercase": true, "lowercase": true,
@ -87,48 +73,6 @@
"type": "string", "type": "string",
"default": "" "default": ""
}, },
"website1Link": {
"type": "string",
"default": ""
},
"website1Title": {
"type": "string",
"default": ""
},
"website1Image": {
"type": "string",
"default": ""
},
"website2Link": {
"type": "string",
"default": ""
},
"website2Title": {
"type": "string",
"default": ""
},
"website2Image": {
"type": "string",
"default": ""
},
"website3Link": {
"type": "string",
"default": ""
},
"website3Title": {
"type": "string",
"default": ""
},
"website3Image": {
"type": "string",
"default": ""
},
"resetPasswordToken": {
"type": "string"
},
"resetPasswordExpires": {
"type": "string"
},
"completedBonfires": { "completedBonfires": {
"type": [ "type": [
{ {
@ -170,21 +114,10 @@
"type": "number", "type": "number",
"default": 0 "default": 0
}, },
"needsSomeDataModeled": {
"type": "boolean",
"default": false
},
"needsMigration": {
"type": "boolean",
"default": true
},
"sendMonthlyEmail": { "sendMonthlyEmail": {
"type": "boolean", "type": "boolean",
"default": true "default": true
}, },
"challengesHash": {
"type": {}
},
"currentChallenge": { "currentChallenge": {
"type": {} "type": {}
}, },
@ -205,10 +138,6 @@
} }
], ],
"default": [] "default": []
},
"uncompletedChallenges": {
"type": "array",
"default": []
} }
}, },
"validations": [], "validations": [],

View File

@ -577,6 +577,7 @@ thead {
margin: 0 auto; margin: 0 auto;
position: relative; position: relative;
top: 50%; top: 50%;
-webkit-transform: translateY(-50%);
transform: translateY(-50%); transform: translateY(-50%);
} }
@ -612,6 +613,11 @@ thead {
color: #009900 color: #009900
} }
.default-border-radius {
border-radius: 5px;
}
.testimonial-copy { .testimonial-copy {
font-size: 20px; font-size: 20px;
text-align: center; text-align: center;

View File

@ -12,7 +12,7 @@
"Bootstrap will figure out how wide your screen is and respond by resizing your HTML elements - hence the name <code>Responsive Design</code>.", "Bootstrap will figure out how wide your screen is and respond by resizing your HTML elements - hence the name <code>Responsive Design</code>.",
"With responsive design, there is no need to design a mobile version of your website. It will look good on devices with screens of any width.", "With responsive design, there is no need to design a mobile version of your website. It will look good on devices with screens of any width.",
"You can add Bootstrap to any app just by including it with <code>&#60;link rel='stylesheet' href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css'/&#62;</code> at the top of your HTML. But we've gone ahead and automatically added it to your Cat Photo App for you.", "You can add Bootstrap to any app just by including it with <code>&#60;link rel='stylesheet' href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css'/&#62;</code> at the top of your HTML. But we've gone ahead and automatically added it to your Cat Photo App for you.",
"To get started, we should nest all of our HTML in a <code>div</code> element with the class \"fluid-container\"." "To get started, we should nest all of our HTML in a <code>div</code> element with the class \"container-fluid\"."
], ],
"tests": [ "tests": [
"assert($('div').hasClass('container-fluid'), 'Your <code>div</code> element should have the class \"row\"')", "assert($('div').hasClass('container-fluid'), 'Your <code>div</code> element should have the class \"row\"')",
@ -1566,7 +1566,7 @@
"difficulty": 2.18, "difficulty": 2.18,
"description": [ "description": [
"Now let's make sure all the content on your page is mobile-responsive.", "Now let's make sure all the content on your page is mobile-responsive.",
"Let's nest your <code>h3</code> element within a <code>div<code> element with the class \"containter-fluid\"." "Let's nest your <code>h3</code> element within a <code>div<code> element with the class \"container-fluid\"."
], ],
"tests": [ "tests": [
"assert($('div').hasClass('container-fluid'), 'Your <code>div</code> element should have the class \"container-fluid\"')", "assert($('div').hasClass('container-fluid'), 'Your <code>div</code> element should have the class \"container-fluid\"')",
@ -1743,7 +1743,7 @@
], ],
"tests": [ "tests": [
"assert($('.btn').length > 5, 'Apply the \"btn\" class to each of your <code>button</code> elements.')", "assert($('.btn').length > 5, 'Apply the \"btn\" class to each of your <code>button</code> elements.')",
"assert($('.btn').length > 5, 'Apply the \"btn-default\" class to each of your <code>button</code> elements.')" "assert($('.btn-default').length > 5, 'Apply the \"btn-default\" class to each of your <code>button</code> elements.')"
], ],
"challengeSeed": [ "challengeSeed": [
"<div class='container-fluid'>", "<div class='container-fluid'>",

View File

@ -4,7 +4,7 @@ module.exports = function(app) {
router.get('/nonprofit-project-instructions', function(req, res) { router.get('/nonprofit-project-instructions', function(req, res) {
res.redirect( res.redirect(
301, 301,
"https://github.com/FreeCodeCamp/freecodecamp/wiki/How-Free-Code-Camp's-Nonprofit-Projects-work" "//github.com/FreeCodeCamp/freecodecamp/wiki/How-Free-Code-Camp's-Nonprofit-Projects-work"
); );
}); });
@ -14,7 +14,7 @@ module.exports = function(app) {
router.get('/privacy', function(req, res) { router.get('/privacy', function(req, res) {
res.redirect( res.redirect(
301, "https://github.com/FreeCodeCamp/freecodecamp/wiki/Free-Code-Camp's-Privacy-Policy" 301, "//github.com/FreeCodeCamp/freecodecamp/wiki/Free-Code-Camp's-Privacy-Policy"
); );
}); });
@ -22,6 +22,10 @@ module.exports = function(app) {
res.redirect(301, '/map'); res.redirect(301, '/map');
}); });
router.get('/field-guide/*', function(req, res) {
res.redirect(302, '//github.com/freecodecamp/freecodecamp/wiki')
});
router.get('/about', function(req, res) { router.get('/about', function(req, res) {
res.redirect(301, '/map'); res.redirect(301, '/map');
}); });

View File

@ -214,19 +214,7 @@ module.exports = function(app) {
bio: user.bio, bio: user.bio,
picture: user.picture, picture: user.picture,
progressTimestamps: user.progressTimestamps, progressTimestamps: user.progressTimestamps,
website1Link: user.website1Link,
website1Title: user.website1Title,
website1Image: user.website1Image,
website2Link: user.website2Link,
website2Title: user.website2Title,
website2Image: user.website2Image,
website3Link: user.website3Link,
website3Title: user.website3Title,
website3Image: user.website3Image,
challenges: challenges, challenges: challenges,
bonfires: user.completedChallenges.filter(function(challenge) {
return challenge.challengeType === 5;
}),
calender: data, calender: data,
moment: moment, moment: moment,
longestStreak: user.longestStreak + longestStreak: user.longestStreak +
@ -290,35 +278,12 @@ module.exports = function(app) {
return res.redirect('/account'); return res.redirect('/account');
} }
var body = req.body || {}; var body = req.body || {};
user.email = body.email.trim() || '';
user.name = body.name.trim() || '';
user.username = body.username.trim() || '';
user.location = body.location.trim() || '';
user.githubProfile = body.githubProfile.trim() || '';
user.facebookProfile = body.facebookProfile.trim() || ''; user.facebookProfile = body.facebookProfile.trim() || '';
user.linkedinProfile = body.linkedinProfile.trim() || ''; user.linkedinProfile = body.linkedinProfile.trim() || '';
user.codepenProfile = body.codepenProfile.trim() || ''; user.codepenProfile = body.codepenProfile.trim() || '';
user.twitterHandle = body.twitterHandle.trim() || ''; user.twitterHandle = body.twitterHandle.trim() || '';
user.bio = body.bio.trim() || ''; user.bio = body.bio.trim() || '';
user.picture = body.picture.trim() ||
'https://s3.amazonaws.com/freecodecamp/' +
'camper-image-placeholder.png';
user.website1Title = body.website1Title.trim() || '';
user.website1Link = body.website1Link.trim() || '';
user.website1Image = body.website1Image.trim() || '';
user.website2Title = body.website2Title.trim() || '';
user.website2Link = body.website2Link.trim() || '';
user.website2Image = body.website2Image.trim() || '';
user.website3Title = body.website3Title.trim() || '';
user.website3Link = body.website3Link.trim() || '';
user.website3Image = body.website3Image.trim() || '';
user.save(function(err) { user.save(function(err) {
if (err) { if (err) {
return next(err); return next(err);

View File

@ -3,30 +3,11 @@ var pmx = require('pmx');
pmx.init(); pmx.init();
var assign = require('lodash').assign, var assign = require('lodash').assign,
loopback = require('loopback'), loopback = require('loopback'),
boot = require('loopback-boot'), boot = require('loopback-boot'),
accepts = require('accepts'), expressState = require('express-state'),
cookieParser = require('cookie-parser'), path = require('path'),
compress = require('compression'), passportProviders = require('./passport-providers');
session = require('express-session'),
expressState = require('express-state'),
logger = require('morgan'),
errorHandler = require('errorhandler'),
methodOverride = require('method-override'),
bodyParser = require('body-parser'),
helmet = require('helmet'),
MongoStore = require('connect-mongo')(session),
flash = require('express-flash'),
path = require('path'),
expressValidator = require('express-validator'),
lessMiddleware = require('less-middleware'),
passportProviders = require('./passport-providers'),
rxMiddleware = require('./utils/rx').rxMiddleware,
/**
* API keys and Passport configuration.
*/
secrets = require('./../config/secrets');
var generateKey = var generateKey =
require('loopback-component-passport/lib/models/utils').generateKey; require('loopback-component-passport/lib/models/utils').generateKey;
@ -45,147 +26,9 @@ var passportConfigurator = new PassportConfigurator(app);
app.set('port', process.env.PORT || 3000); app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views')); app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade'); app.set('view engine', 'jade');
app.use(compress());
app.use(lessMiddleware(path.join(__dirname, '/public')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(expressValidator({
customValidators: {
matchRegex: function(param, regex) {
return regex.test(param);
}
}
}));
app.use(methodOverride());
app.use(cookieParser(secrets.cookieSecret));
app.use(session({
resave: true,
saveUninitialized: true,
secret: secrets.sessionSecret,
store: new MongoStore({
url: secrets.db,
'autoReconnect': true
})
}));
app.use(flash());
app.disable('x-powered-by'); app.disable('x-powered-by');
// adds passport initialization after session middleware phase is complete // adds passport initialization after session middleware phase is complete
app.use(helmet.xssFilter());
app.use(helmet.noSniff());
app.use(helmet.frameguard());
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
next();
});
var trusted = [
"'self'",
'blob:',
'104.236.218.15',
'*.freecodecamp.com',
'http://www.freecodecamp.com',
'https://www.freecodecamp.com',
'https://freecodecamp.com',
'https://freecodecamp.org',
'*.freecodecamp.org',
// NOTE(berks): add the following as the blob above was not covering www
'http://www.freecodecamp.org',
'ws://freecodecamp.com/',
'ws://www.freecodecamp.com/',
'*.gstatic.com',
'*.google-analytics.com',
'*.googleapis.com',
'*.google.com',
'*.gstatic.com',
'*.doubleclick.net',
'*.twitter.com',
'*.twitch.tv',
'*.twimg.com',
"'unsafe-eval'",
"'unsafe-inline'",
'*.bootstrapcdn.com',
'*.cloudflare.com',
'https://*.cloudflare.com',
'localhost:3001',
'ws://localhost:3001/',
'http://localhost:3001',
'localhost:3000',
'ws://localhost:3000/',
'http://localhost:3000',
'127.0.0.1',
'127.0.0.1:3000',
'ws://127.0.0.1:3000/',
'http://127.0.0.1:3000',
'*.ionicframework.com',
'https://syndication.twitter.com',
'*.youtube.com',
'*.jsdelivr.net',
'https://*.jsdelivr.net',
'*.ytimg.com',
'*.bitly.com',
'http://cdn.inspectlet.com/',
'https://cdn.inspeclet.com/',
'wss://inspectletws.herokuapp.com/',
'http://hn.inspectlet.com/',
'*.googleapis.com',
'*.gstatic.com',
'https://hn.inspectlet.com/',
'http://*.github.com'
];
app.use(helmet.csp({
defaultSrc: trusted,
scriptSrc: [
'*.optimizely.com',
'*.aspnetcdn.com',
'*.d3js.org',
'https://cdn.inspectlet.com/inspectlet.js',
'http://cdn.inspectlet.com/inspectlet.js',
'http://beta.freecodecamp.com'
].concat(trusted),
'connect-src': [
'vimeo.com'
].concat(trusted),
styleSrc: [
'*.googleapis.com',
'*.gstatic.com'
].concat(trusted),
imgSrc: [
/* allow all input since we have user submitted images for public profile*/
'*'
].concat(trusted),
fontSrc: [
'*.googleapis.com',
'*.gstatic.com'
].concat(trusted),
mediaSrc: [
'*.amazonaws.com',
'*.twitter.com'
].concat(trusted),
frameSrc: [
'*.gitter.im',
'*.gitter.im https:',
'*.vimeo.com',
'*.twitter.com',
'*.ghbtns.com'
].concat(trusted),
// set to true if you only want to report errors
reportOnly: false,
// set to true if you want to set all headers
setAllHeaders: false,
// set to true if you want to force buggy CSP in Safari 5
safari5: false
}));
passportConfigurator.init(); passportConfigurator.init();
boot(app, { boot(app, {
@ -222,6 +65,9 @@ var passportOptions = {
if (email) { if (email) {
userObj.email = email; userObj.email = email;
} }
if (provider === 'github-login') {
userObj.isGithubCool = true;
}
return userObj; return userObj;
} }
}; };

View File

@ -2,108 +2,27 @@ extends ../layout
block content block content
script. script.
var challengeName = 'Account View' var challengeName = 'Account View'
.panel.panel-info.min-height-1000(ng-controller="profileValidationController") .panel.panel-info(ng-controller="profileValidationController")
.panel-heading.text-center Update your portfolio here: .panel-heading.text-center Update your portfolio here:
.panel-body .panel-body
.container.text-center if (!user.github)
.col-xs-12
a.btn.btn-lg.btn-block.btn-github.btn-link-social(href='/auth/github')
i.fa.fa-github
| Link GitHub with my account
.col-xs-12
form.form-horizontal(action='/account/profile', method='POST', novalidate='novalidate', name='profileForm' ng-show="asyncComplete") form.form-horizontal(action='/account/profile', method='POST', novalidate='novalidate', name='profileForm' ng-show="asyncComplete")
input(type='hidden', name='_csrf', value=_csrf) input(type='hidden', name='_csrf', value=_csrf)
.col-sm-4.col-sm-offset-5
h2 Bio
.form-group .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='name') Name * label.col-sm-3.col-sm-offset-1.control-label(for='bio') Bio (140 characters)
.col-sm-4
input.form-control(type='text', placeholder='Name', name='name', autocomplete="off", ng-model='user.name', ng-minlength='3', ng-maxlength='50', required='required', id='name')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.name.$invalid && profileForm.name.$error.required")
alert(type='danger')
span.ion-close-circled(id='#name-error')
| Your name is required.
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show='profileForm.name.$error.minlength && !profileForm.name.$pristine')
alert(type='danger')
span.ion-close-circled
| Your name must be at least 3 characters.
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show='profileForm.name.$error.maxlength && !profileForm.name.$pristine')
alert(type='danger')
span.ion-close-circled
| Your name must be fewer than 50 characters.
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='username') Username (path to public profile) *
.col-sm-4
input.form-control(type='text', placeholder='username' name='username', autocomplete="off", id='username', ng-model='user.username', required='required', ng-minlength='5', ng-maxlength='20', ng-keypress='', unique-username='', ng-pattern="/^[A-z0-9_]+$/")
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.username.$error.pattern")
alert(type='danger')
span.ion-close-circled
| Your username should only contain letters, numbers and underscores (az10_).
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.username.$error.required")
alert(type='danger')
span.ion-close-circled
| Your username is required.
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.username.$error.minlength && !profileForm.username.$pristine")
alert(type='danger')
span.ion-close-circled
| Your username must be at least 5 characters.
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.username.$error.maxlength && !profileForm.username.$pristine")
alert(type='danger')
span.ion-close-circled
| Your username must be fewer than 15 characters.
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.username.$error.unique && !profileForm.username.$pristine && $scope.storedUsername !== user.username")
alert(type='danger')
span.ion-close-circled
| That username is already in use.
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') Email *
.col-sm-4
input.form-control(type='email', name='email', id='email', autocomplete="off", ng-model='user.email', required='required', ng-keypress='', unique-email='')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.email.$error.required")
alert(type='danger')
span.ion-close-circled
| Your email address is required.
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.$error.email && !profileForm.email.$pristine")
alert(type='danger')
span.ion-close-circled
| Please enter a valid email format.
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.email.$error.unique && !profileForm.email.$pristine")
alert(type='danger')
span.ion-close-circled
| That email is already in use.
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='location') Location
.col-sm-4
input.form-control(type='text', name='location', autocomplete="off", id='location', ng-model='user.location')
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') Link to Profile Photo (1:1 ratio)
.col-sm-4
input.form-control(type='url', name='picture', id='picture', ng-model='user.picture', placeholder='http://www.example.com/image.jpg')
.col-sm-4.col-sm-offset-5(ng-show="profileForm.picture.$error.url && !profileForm.picture.$pristine")
alert(type='danger')
span.ion-close-circled
| Please enter a valid URL format (http://www.example.com/image.jpg).
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='bio') Bio (140 characters)
.col-sm-4 .col-sm-4
input.form-control(type='text', name='bio', autocomplete="off", ng-model='user.bio', ng-maxlength='140', id='bio') input.form-control(type='text', name='bio', autocomplete="off", ng-model='user.bio', ng-maxlength='140', id='bio')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show='profileForm.bio.$error.maxlength && !profileForm.bio.$pristine') .col-sm-4.col-sm-offset-5(ng-cloak, ng-show='profileForm.bio.$error.maxlength && !profileForm.bio.$pristine')
alert(type='danger') alert(type='danger')
span.ion-close-circled span.ion-close-circled
| Your bio must be fewer than 140 characters. | Your bio must be fewer than 140 characters.
.form-group .form-group
.col-sm-offset-5.col-sm-4 label.col-sm-3.col-sm-offset-1.control-label(for='email') Twitter
button.btn.btn-primary.btn-block(type='submit', ng-disabled='profileForm.$invalid')
span.ion-edit
| Update my Bio
.col-sm-4.col-sm-offset-5
h2 Social Profiles
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') Twitter
.col-sm-4 .col-sm-4
.input-group.twitter-input .input-group.twitter-input
span.input-group-addon @ span.input-group-addon @
@ -116,17 +35,9 @@ block content
alert(type='danger') alert(type='danger')
span.ion-close-circled span.ion-close-circled
| Your name must be fewer than 15 characters. | Your name must be fewer than 15 characters.
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') GitHub
.col-sm-4
input.form-control(type='url', name='githubProfile', id='githubProfile', autocomplete="off", ng-model='user.githubProfile', placeholder='http://')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.githubProfile.$error.url && !profileForm.githubProfile.$pristine")
alert(type='danger')
span.ion-close-circled
| Please enter a valid URL format (http://www.example.com).
.form-group .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') CodePen label.col-sm-3.col-sm-offset-1.control-label(for='email') CodePen
.col-sm-4 .col-sm-4
input.form-control(type='url', name='codepenProfile', id='codepenProfile', autocomplete="off", ng-model='user.codepenProfile', placeholder='http://') input.form-control(type='url', name='codepenProfile', id='codepenProfile', autocomplete="off", ng-model='user.codepenProfile', placeholder='http://')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.codepenProfile.$error.url && !profileForm.codepenProfile.$pristine") .col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.codepenProfile.$error.url && !profileForm.codepenProfile.$pristine")
@ -135,7 +46,7 @@ block content
| Please enter a valid URL format (http://www.example.com). | Please enter a valid URL format (http://www.example.com).
.form-group .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') LinkedIn label.col-sm-3.col-sm-offset-1.control-label(for='email') LinkedIn
.col-sm-4 .col-sm-4
input.form-control(type='url', name='linkedinProfile', id='linkedinProfile', autocomplete="off", ng-model='user.linkedinProfile', placeholder='http://') input.form-control(type='url', name='linkedinProfile', id='linkedinProfile', autocomplete="off", ng-model='user.linkedinProfile', placeholder='http://')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.linkedinProfile.$error.url && !profileForm.linkedinProfile.$pristine") .col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.linkedinProfile.$error.url && !profileForm.linkedinProfile.$pristine")
@ -144,7 +55,7 @@ block content
| Please enter a valid URL format (http://www.example.com). | Please enter a valid URL format (http://www.example.com).
.form-group .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') Facebook label.col-sm-3.col-sm-offset-1.control-label(for='email') Facebook
.col-sm-4 .col-sm-4
input.form-control(type='url', name='facebookProfile', id='facebookProfile', autocomplete="off", ng-model='user.facebookProfile', placeholder='http://') input.form-control(type='url', name='facebookProfile', id='facebookProfile', autocomplete="off", ng-model='user.facebookProfile', placeholder='http://')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.facebookProfile.$error.url && !profileForm.facebookProfile.$pristine") .col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.facebookProfile.$error.url && !profileForm.facebookProfile.$pristine")
@ -152,120 +63,14 @@ block content
span.ion-close-circled span.ion-close-circled
| Please enter a valid URL format (http://www.example.com). | Please enter a valid URL format (http://www.example.com).
.form-group button.btn.btn-lg.btn-block.btn-primary.btn-link-social(type='submit', ng-disabled='profileForm.$invalid')
.col-sm-offset-5.col-sm-4 span.ion-edit
button.btn.btn-primary.btn-block(type='submit', ng-disabled='profileForm.$invalid') | Update my info
span.ion-edit
| Update my Social Links
.col-sm-4.col-sm-offset-5.negative-bottom
h2 Portfolio
.col-sm-4.col-sm-offset-5.flat-top
h3 First Portfolio Project
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website1Title') Title
.col-sm-4
input.form-control(type='text', name='website1Title', id='website1Title', autocomplete="off", ng-model='user.website1Title', ng-maxlength='140')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.website1Title.$error.maxlength && !profileForm.website1Title.$pristine")
alert(type='danger')
span.ion-close-circled
| Portfolio project title must be fewer than 140 characters.
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website1Link') Link
.col-sm-4
input.form-control(type='url', name='website1Link', id='website1Link', autocomplete="off", ng-model='user.website1Link', placeholder='http://')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.website1Link.$error.url && !profileForm.website1Link.$pristine")
alert(type='danger')
span.ion-close-circled
| Please enter a valid URL format (http://www.example.com).
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website1Image') Image Link (4:3 ratio)
.col-sm-4
input.form-control(type='url', name='website1Image', id='website1Image', autocomplete="off", ng-model='user.website1Image', placeholder='http://www.example.com/image.jpg')
.col-sm-4.col-sm-offset-5(ng-show="profileForm.website1Image.$error.url && !profileForm.website1Image.$pristine")
alert(type='danger')
span.ion-close-circled
| Please enter a valid URL format (http://www.example.com/image.jpg).
.col-sm-4.col-sm-offset-5.flat-top
h3 Second Portfolio Project
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website2Title') Title
.col-sm-4
input.form-control(type='text', name='website2Title', id='website2Title', autocomplete="off", ng-model='user.website2Title', ng-maxlength='140')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.website2Title.$error.maxlength && !profileForm.website2Title.$pristine")
alert(type='danger')
span.ion-close-circled
| Portfolio project title must be fewer than 140 characters.
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website2Link') Link
.col-sm-4
input.form-control(type='url', name='website2Link', id='website2Link', autocomplete="off", ng-model='user.website2Link', placeholder='http://')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.website2Link.$error.url && !profileForm.website2Link.$pristine")
alert(type='danger')
span.ion-close-circled
| Please enter a valid URL format (http://www.example.com).
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website2Image') Image Link (4:3 ratio)
.col-sm-4
input.form-control(type='url', name='website2Image', id='website2Image', autocomplete="off", ng-model='user.website2Image', placeholder='http://www.example.com/image.jpg')
.col-sm-4.col-sm-offset-5(ng-show="profileForm.website2Image.$error.url && !profileForm.website2Image.$pristine")
alert(type='danger')
span.ion-close-circled
| Please enter a valid URL format (http://www.example.com/image.jpg).
.col-sm-4.col-sm-offset-5.flat-top
h3 Third Portfolio Project
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website3Title') Title
.col-sm-4
input.form-control(type='text', name='website3Title', id='website3Title', autocomplete="off", ng-model='user.website3Title', ng-maxlength='140')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.website3Title.$error.maxlength && !profileForm.website3Title.$pristine")
alert(type='danger')
span.ion-close-circled
| Portfolio project title must be fewer than 140 characters.
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website3Link') Link
.col-sm-4
input.form-control(type='url', name='website3Link', id='website3Link', autocomplete="off", ng-model='user.website3Link', placeholder='http://')
.col-sm-4.col-sm-offset-5(ng-cloak, ng-show="profileForm.website3Link.$error.url && !profileForm.website3Link.$pristine")
alert(type='danger')
span.ion-close-circled
| Please enter a valid URL format (http://www.example.com).
.form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website3Image') Image Link (4:3 ratio)
.col-sm-4
input.form-control(type='url', name='website3Image', id='website3Image', autocomplete="off", ng-model='user.website3Image', placeholder='http://www.example.com/image.jpg', ng-pattern='/[\.](jpg|png|jpeg|gif)(\s+)?$/')
.col-sm-4.col-sm-offset-5(ng-show="profileForm.website3Image.$error.url && !profileForm.website3Image.$pristine")
alert(type='danger')
span.ion-close-circled
| Please enter a valid URL format (http://www.example.com/image.jpg).
.form-group
.col-sm-offset-5.col-sm-4
button.btn.btn-primary.btn-block(type='submit', ng-disabled='profileForm.$invalid')
span.ion-edit
| Update my Portfolio
br
.panel.panel-info .panel.panel-info
.panel-heading.text-center Manage your account here: .panel-heading.text-center Manage your account here:
.panel-body .panel-body
if (!user.google || !user.facebook || !user.linkedin || !user.twitter) if (!user.google || !user.facebook || !user.linkedin || !user.twitter)
if (!user.github)
.col-xs-12
a.btn.btn-lg.btn-block.btn-github.btn-link-social(href='/auth/github')
i.fa.fa-github
| Link GitHub with my account
if (!user.twitter) if (!user.twitter)
.col-xs-12 .col-xs-12
a.btn.btn-lg.btn-block.btn-twitter.btn-link-social.disabled(href='#') a.btn.btn-lg.btn-block.btn-twitter.btn-link-social.disabled(href='#')

View File

@ -96,21 +96,3 @@ block content
td.col-xs-2= moment(challenge.completedDate, 'x').format("MMM DD, YYYY") td.col-xs-2= moment(challenge.completedDate, 'x').format("MMM DD, YYYY")
td.col-xs-6 td.col-xs-6
a(href=challenge.solution, target='_blank') View my solution a(href=challenge.solution, target='_blank') View my solution
br
if (bonfires.length > 0)
.col-sm-12
table.table.table-striped
thead
tr
th.col-xs-4 Bonfire
th.col-xs-2 Completed
th.col-xs-6 Solution
for bonfire in bonfires
tr
td.col-xs-4
a(href='/challenges/' + bonfire.name, target='_blank')= bonfire.name
td.col-xs-2= moment(bonfire.completedDate, 'x').format("MMM DD, YYYY")
td.col-xs-6
pre.wrappable= bonfire.solution
br

View File

@ -3,6 +3,9 @@ block content
script. script.
var completedChallenges = !{JSON.stringify(completedChallengeList)}; var completedChallenges = !{JSON.stringify(completedChallengeList)};
var challengeList = !{JSON.stringify(challengeList)}; var challengeList = !{JSON.stringify(challengeList)};
.bg-danger.default-border-radius
p &nbsp;&nbsp;&nbsp;&nbsp;
a(href='https://github.com/FreeCodeCamp/freecodecamp/wiki/beta' target='_blank') You're using our experimental beta site. None of your progress here will be saved. Please click this to learn more.
.panel.panel-info .panel.panel-info
.panel-heading.text-center .panel-heading.text-center
h1 Challenge Map h1 Challenge Map

View File

@ -1,8 +1,8 @@
extends layout extends layout
block content block content
.bg-danger .bg-danger.default-border-radius
a(href='https://github.com/FreeCodeCamp/freecodecamp/wiki/beta before continuing') p &nbsp;&nbsp;&nbsp;&nbsp;
h3 Warning! You are on beta! Please read this link before continuing! https://github.com/freecodecamp/freecodecamp/wiki/beta a(href='https://github.com/FreeCodeCamp/freecodecamp/wiki/beta' target='_blank') You're using our experimental beta site. None of your progress here will be saved. Please click this to learn more.
.jumbotron .jumbotron
.text-center .text-center
h1.hug-top Code with Us h1.hug-top Code with Us

View File

@ -7,10 +7,6 @@ nav.navbar.navbar-default.navbar-fixed-top.nav-height
img.img-responsive.nav-logo(src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg.gz', alt='learn to code javascript at Free Code Camp logo') img.img-responsive.nav-logo(src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg.gz', alt='learn to code javascript at Free Code Camp logo')
.collapse.navbar-collapse .collapse.navbar-collapse
ul.nav.navbar-nav.navbar-right.hamburger-dropdown ul.nav.navbar-nav.navbar-right.hamburger-dropdown
if user
li
a(href='/challenges') Learn
li li
a(href='/map') Map a(href='/map') Map
li li
@ -18,7 +14,7 @@ nav.navbar.navbar-default.navbar-fixed-top.nav-height
li li
a(href='/news') News a(href='/news') News
li li
a(href='//github.com/FreeCodeCamp/freecodecamp/wiki/Home') Wiki a(href='//github.com/FreeCodeCamp/freecodecamp/wiki/Home', target='_blank') Wiki
if !user if !user
li &thinsp; &thinsp; &thinsp; li &thinsp; &thinsp; &thinsp;
li li