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

:ting with '#' will be ignored, and an empty message aborts
:
aad]
dad
This commit is contained in:
benmcmahon100
2015-10-06 22:34:20 +01:00
24 changed files with 592 additions and 106 deletions

View File

@ -879,16 +879,55 @@ common.init.push((function() {
}
next();
});
}
function handleActionClick() {
$(this)
function handleActionClick(e) {
var props = common.challengeSeed[0] ||
{ stepIndex: [] };
var $el = $(this);
var index = +$el.attr('id');
var propIndex = props.stepIndex.indexOf(index);
if (propIndex === -1) {
return $el
.parent()
.find('.disabled')
.removeClass('disabled');
}
// an API action
// prevent link from opening
e.preventDefault();
var prop = props.properties[propIndex];
var api = props.apis[propIndex];
if (common[prop]) {
return $el
.parent()
.find('.disabled')
.removeClass('disabled');
}
$
.post(api)
.done(function(data) {
// assume a boolean indicates passing
if (typeof data === 'boolean') {
return $el
.parent()
.find('.disabled')
.removeClass('disabled');
}
// assume api returns string when fails
$el
.parent()
.find('.disabled')
.replaceWith('<p>' + data + '</p>');
})
.fail(function() {
console.log('failed');
});
}
function handleFinishClick(e) {
e.preventDefault();
$(submitModalId).modal('show');

View File

@ -102,14 +102,31 @@
},
"isLocked": {
"type": "boolean",
"default": false
"default": false,
"description": "Campers profile does not show challenges/certificates to the public"
},
"currentChallenge": {
"type": {}
},
"isUniqMigrated": {
"type": "boolean",
"default": false
"default": false,
"description": "Campers completedChallenges array is free of duplicates"
},
"isHonest": {
"type": "boolean",
"default": false,
"description": "Camper has signed academic honesty policy"
},
"isFrontEndCert": {
"type": "boolean",
"defaut": false,
"description": "Camper is front end certified"
},
"isFullStackCert": {
"type": "boolean",
"default": false,
"description": "Campers is full stack certified"
},
"completedChallenges": {
"type": [

BIN
public/fonts/saxmono.ttf Executable file

Binary file not shown.

View File

@ -378,7 +378,7 @@
"truncate(\"A-tisket a-tasket A green and yellow basket\", 11, \"\");"
],
"tests": [
"assert(truncate(\"A-tisket a-tasket A green and yellow basket\", 11) === \"A-tisket...\", 'message: <code>truncate(\"A-tisket a-tasket A green and yellow basket\", 1)</code> should return \"A-tisket...\".');",
"assert(truncate(\"A-tisket a-tasket A green and yellow basket\", 11) === \"A-tisket...\", 'message: <code>truncate(\"A-tisket a-tasket A green and yellow basket\", 11)</code> should return \"A-tisket...\".');",
"assert(truncate(\"Peter Piper picked a peck of pickled peppers\", 14) === \"Peter Piper...\", 'message: <code>truncate(\"Peter Piper picked a peck of pickled peppers\", 14)</code> should return \"Peter Piper...\".');",
"assert(truncate(\"A-tisket a-tasket A green and yellow basket\", \"A-tisket a-tasket A green and yellow basket\".length) === \"A-tisket a-tasket A green and yellow basket\", 'message: <code>truncate(\"A-tisket a-tasket A green and yellow basket\", \"A-tisket a-tasket A green and yellow basket\".length)</code> should return \"A-tisket a-tasket A green and yellow basket\".');",
"assert(truncate('A-tisket a-tasket A green and yellow basket', 'A-tisket a-tasket A green and yellow basket'.length + 2) === 'A-tisket a-tasket A green and yellow basket', 'message: <code>truncate(\"A-tisket a-tasket A green and yellow basket\", \"A-tisket a-tasket A green and yellow basket\".length + 2)</code> should return \"A-tisket a-tasket A green and yellow basket\".');"

View File

@ -1039,7 +1039,7 @@
],
"tests":[
"assert(test === 2, 'message: Your RegEx should have found two numbers in the <code>testString</code>.');",
"assert(editor.getValue().match(/\\/\\\\d\\+\\//gi), 'message: You should be using the following expression <code>/\\\\d+/gi</code> to find the numbers in the <code>testString</code>.');"
"assert(editor.getValue().match(/\\/\\\\d\\+\\//g), 'message: You should be using the following expression <code>/\\d+/g</code> to find the numbers in the <code>testString</code>.');"
],
"challengeSeed":[
"var test = (function() {",
@ -1047,7 +1047,7 @@
"",
" // Only change code below this line.",
"",
" var expression = /.+/gi;",
" var expression = /.+/g;",
"",
" // Only change code above this line.",
" // We use this function to show you the value of your variable in your output box.",
@ -1069,7 +1069,7 @@
],
"tests":[
"assert(test === 7, 'message: Your RegEx should have found seven spaces in the <code>testString</code>.');",
"assert(editor.getValue().match(/\\/\\\\s\\+\\//gi), 'message: You should be using the following expression <code>/\\\\s+/gi</code> to find the spaces in the <code>testString</code>.');"
"assert(editor.getValue().match(/\\/\\\\s\\+\\//g), 'message: You should be using the following expression <code>/\\s+/g</code> to find the spaces in the <code>testString</code>.');"
],
"challengeSeed":[
"var test = (function(){",
@ -1077,7 +1077,7 @@
"",
" // Only change code below this line.",
"",
" var expression = /.+/gi;",
" var expression = /.+/g;",
"",
" // Only change code above this line.",
" // We use this function to show you the value of your variable in your output box.",
@ -1092,12 +1092,12 @@
"title": "Invert Regular Expression Matches with JavaScript",
"difficulty":"9.987",
"description":[
"Use <code>/\\S/gi</code> to match everything that isn't a space in the string.",
"Use <code>/\\S/g</code> to match everything that isn't a space in the string.",
"You can invert any match by using the uppercase version of the selector <code>\\s</code> versus <code>\\S</code> for example."
],
"tests":[
"assert(test === 49, 'message: Your RegEx should have found forty nine non-space characters in the <code>testString</code>.');",
"assert(editor.getValue().match(/\\/\\\\S\\/gi/gi), 'message: You should be using the following expression <code>/\\\\S/gi</code> to find non-space characters in the <code>testString</code>.');"
"assert(editor.getValue().match(/\\/\\\\S\\/g/g), 'message: You should be using the following expression <code>/\\S/g</code> to find non-space characters in the <code>testString</code>.');"
],
"challengeSeed":[
"var test = (function(){",
@ -1105,7 +1105,7 @@
"",
" // Only change code below this line.",
"",
" var expression = /./gi;",
" var expression = /./g;",
"",
" // Only change code above this line.",
" // We use this function to show you the value of your variable in your output box.",

View File

@ -9,7 +9,7 @@
"Now let's go back to our Cat Photo App. This time, we'll style it using the popular Bootstrap responsive CSS framework.",
"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.",
"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 added it for you to this page behind the scenes.",
"To get started, we should nest all of our HTML in a <code>div</code> element with the class <code>container-fluid</code>."
],
"tests": [

View File

@ -5,13 +5,36 @@
{
"id": "561add10cb82ac38a17513be",
"title": "Claim Your Front End Development Certificate",
"difficulty": 0.00,
"challengeSeed": [],
"challengeSeed": [
{
"properties": ["isHonest", "isFrontEndCert"],
"apis": ["/certificate/honest", "/certificate/verify/front-end"],
"stepIndex": [1, 2]
}
],
"description": [
[
"http://i.imgur.com/RlEk2IF.jpg",
"a picture of Free Code Camp's 4 benefits: Get connected, Learn JavaScript, Build your Portfolio, Help nonprofits",
"Welcome to Free Code Camp. We're an open source community of busy people who learn to code and help nonprofits.",
"http://i.imgur.com/luMkKst.jpg",
"An image of our Front End Development Certificate",
"This challenge will give you your verified Front End Development Certificate. Before we issue your certificate, we must verify that you have completed all of our basic and intermediate Bonfires, and all our basic and intermediate Ziplines. You must also accept our Academic Honesty Pledge. Click the button below to start this process.",
""
],
[
"http://i.imgur.com/HArFfMN.jpg",
"The definition of plagiarism: Plagiarism (noun) - copying someone elses work and presenting it as your own without crediting them",
"By clicking below, you pledge that all of your submitted code A) is code you or your pair personally wrote, or B) comes from open source libraries like jQuery, or C) has been clearly attributed to its original authors. You also give us permission to audit your challenge solutions and revoke your certificate if we discover evidence of plagiarism.",
"#"
],
[
"http://i.imgur.com/14F2Van.jpg",
"An image of the text \"Front End Development Certificate requirements\"",
"Let's confirm that you have completed all of our basic and intermediate Bonfires, and all our basic and intermediate Ziplines. Click the button below to verify this.",
"#"
],
[
"http://i.imgur.com/16SIhHO.jpg",
"An image of the word \"Congratulations\"",
"Congratulations! We've added your Front End Development Certificate to your certificate to your portfolio page. Unless you choose to hide your solutions, this certificate will remain publicly visible and verifiable.",
""
]
],

View File

@ -6,12 +6,36 @@
"id": "660add10cb82ac38a17513be",
"title": "Claim Your Full Stack Development Certificate",
"difficulty": 0.00,
"challengeSeed": [],
"challengeSeed": [
{
"properties": ["isHonest", "isFullStackCert"],
"apis": ["/certificate/honest", "/certificate/verify/full-stack"],
"stepIndex": [1, 2]
}
],
"description": [
[
"http://i.imgur.com/RlEk2IF.jpg",
"a picture of Free Code Camp's 4 benefits: Get connected, Learn JavaScript, Build your Portfolio, Help nonprofits",
"Welcome to Free Code Camp. We're an open source community of busy people who learn to code and help nonprofits.",
"http://i.imgur.com/qXublEe.jpg",
"An image of our Full Stack Development Certificate",
"This challenge will give you your verified Full Stack Development Certificate. Before we issue your certificate, we must verify that you have completed all of Bonfires, Ziplines and Basejumps. You must also accept our Academic Honesty Pledge. Click the button below to start this process.",
""
],
[
"http://i.imgur.com/HArFfMN.jpg",
"The definition of plagiarism: Plagiarism (noun) - copying someone elses work and presenting it as your own without crediting them",
"By clicking below, you pledge that all of your submitted code A) is code you or your pair personally wrote, or B) comes from open source libraries like jQuery, or C) has been clearly attributed to its original authors. You also give us permission to audit your challenge solutions and revoke your certificate if we discover evidence of plagiarism.",
"#"
],
[
"http://i.imgur.com/2qn7tHp.jpg",
"An image of the text \"Full Stack Development Certificate requirements\"",
"Let's confirm that you have completed all of our Bonfires, Ziplines and Basejumps. Click the button below to verify this.",
"#"
],
[
"http://i.imgur.com/16SIhHO.jpg",
"An image of the word \"Congratulations\"",
"Congratulations! We've added your Full Stack Development Certificate to your certificate to your portfolio page. Unless you choose to hide your solutions, this certificate will remain publicly visible and verifiable.",
""
]
],

View File

@ -242,7 +242,7 @@
[
"",
"",
"Free Code Camp will always be free. If you want to feel more motivated to earn our certificates faster, we encourage you to instead donate each month to a nonprofit.",
"Free Code Camp will always be free. If you want to feel more motivated to earn our certificates faster, we encourage you to instead pledge to donate to a nonprofit each day.",
""
]
],

151
server/boot/certificate.js Normal file
View File

@ -0,0 +1,151 @@
import _ from 'lodash';
import dedent from 'dedent';
import { Observable } from 'rx';
import debugFactory from 'debug';
import {
ifNoUser401,
ifNoUserSend
} from '../utils/middleware';
import {
saveUser,
observeQuery
} from '../utils/rx';
import {
frontEndChallangeId,
fullStackChallangeId
} from '../utils/constantStrings.json';
const debug = debugFactory('freecc:certification');
const sendMessageToNonUser = ifNoUserSend(
'must be logged in to complete.'
);
function isCertified(frontEndIds, { completedChallenges, isFrontEndCert }) {
if (isFrontEndCert) {
return true;
}
return _.every(frontEndIds, ({ id }) => _.some(completedChallenges, { id }));
}
export default function certificate(app) {
const router = app.loopback.Router();
const { Challenge } = app.models;
const frontEndChallangeIds$ = observeQuery(
Challenge,
'findById',
frontEndChallangeId,
{
id: true,
tests: true,
name: true,
challengeType: true
}
)
.shareReplay();
const fullStackChallangeIds$ = observeQuery(
Challenge,
'findById',
fullStackChallangeId,
{
id: true,
tests: true,
name: true,
challengeType: true
}
)
.shareReplay();
router.post(
'/certificate/verify/front-end',
ifNoUser401,
verifyCert
);
router.post(
'/certificate/verify/full-stack',
ifNoUser401,
verifyCert
);
router.post(
'/certificate/honest',
sendMessageToNonUser,
postHonest
);
app.use(router);
function verifyCert(req, res, next) {
const isFront = req.path.split('/').pop() === 'front-end';
Observable.just({})
.flatMap(() => {
if (isFront) {
return frontEndChallangeIds$;
}
return fullStackChallangeIds$;
})
.flatMap(challenge => {
const { user } = req;
const {
id,
tests,
name,
challengeType
} = challenge;
if (
isFront && !user.isFrontEndCert && isCertified(tests, user) ||
!isFront && !user.isFullStackCert && isCertified(tests, user)
) {
debug('certified');
if (isFront) {
user.isFrontEndCert = true;
} else {
user.isFullStackCert = true;
}
user.completedChallenges.push({
id,
name,
completedDate: new Date(),
challengeType
});
return saveUser(user);
}
return Observable.just(user);
})
.subscribe(
user => {
if (
isFront && user.isFrontEndCert ||
!isFront && user.isFullStackCert
) {
return res.status(200).send(true);
}
return res.status(200).send(
dedent`
Looks like you have not completed the neccessary steps,
Please return the map
`
);
},
next
);
}
function postHonest(req, res, next) {
const { user } = req;
user.isHonest = true;
saveUser(user)
.subscribe(
(user) => {
res.status(200).send(!!user.isHonest);
},
next
);
}
}

View File

@ -9,11 +9,10 @@ import utils from '../utils';
import {
saveUser,
observeMethod,
observableQueryFromModel
observeQuery
} from '../utils/rx';
import {
userMigration,
ifNoUserSend
} from '../utils/middleware';
@ -147,8 +146,6 @@ module.exports = function(app) {
completedBonfire
);
// the follow routes are covered by userMigration
router.use(userMigration);
router.get('/map', challengeMap);
router.get(
'/challenges/next-challenge',
@ -183,9 +180,9 @@ module.exports = function(app) {
'could not find challenge block for ' + challenge.block
);
}
const nextBlock$ = blocks$.elementAt(blockIndex + 1);
const firstChallengeOfNextBlock$ = nextBlock$
.map(block => block.challenges[0]);
const firstChallengeOfNextBlock$ = blocks$
.elementAtOrDefault(blockIndex + 1, {})
.map(({ challenges = [] }) => challenges[0]);
return blocks$
.elementAt(blockIndex)
@ -214,6 +211,9 @@ module.exports = function(app) {
});
})
.map(nextChallenge => {
if (!nextChallenge) {
return null;
}
nextChallengeName = nextChallenge.dashedName;
return nextChallengeName;
})
@ -270,12 +270,13 @@ module.exports = function(app) {
}
if (dasherize(challenge.name) !== origChallengeName) {
return Observable.just(
'/challenges/' +
dasherize(challenge.name) +
'?solution=' +
encodeURIComponent(solutionCode)
);
let redirectUrl = `/challenges/${dasherize(challenge.name)}`;
if (solutionCode) {
redirectUrl += `?solution=${encodeURIComponent(solutionCode)}`;
}
return Observable.just(redirectUrl);
}
// save user does nothing if user does not exist
@ -330,7 +331,7 @@ module.exports = function(app) {
challengeType: 5
};
observableQueryFromModel(
observeQuery(
User,
'findOne',
{ where: { username: ('' + completedWith).toLowerCase() } }
@ -458,7 +459,7 @@ module.exports = function(app) {
verified: false
};
observableQueryFromModel(
observeQuery(
User,
'findOne',
{ where: { username: completedWith.toLowerCase() } }

15
server/boot/commit.js Normal file
View File

@ -0,0 +1,15 @@
export default function commit(app) {
const router = app.loopback.Router();
router.get(
'/commit',
commitToNonprofit
);
app.use(router);
function commitToNonprofit(req, res) {
res.render('commit/', {
title: 'Commit to a nonprofit. Commit to your goal.'
});
}
}

View File

@ -1,8 +1,15 @@
import _ from 'lodash';
import dedent from 'dedent';
import moment from 'moment';
import { Observable } from 'rx';
import debugFactory from 'debug';
import {
frontEndChallangeId,
fullStackChallangeId
} from '../utils/constantStrings.json';
import { ifNoUser401, ifNoUserRedirectTo } from '../utils/middleware';
import { observeQuery } from '../utils/rx';
const debug = debugFactory('freecc:boot:user');
const daysBetween = 1.5;
@ -52,7 +59,16 @@ function dayDiff([head, tail]) {
module.exports = function(app) {
var router = app.loopback.Router();
var User = app.models.User;
// var Story = app.models.Story;
function findUserByUsername$(username, fields) {
return observeQuery(
User,
'findOne',
{
where: { username },
fields
}
);
}
router.get('/login', function(req, res) {
res.redirect(301, '/signin');
@ -85,7 +101,18 @@ module.exports = function(app) {
);
router.get('/vote1', vote1);
router.get('/vote2', vote2);
// Ensure this is the last route!
// Ensure these are the last routes!
router.get(
'/:username/front-end-certification',
showCert
);
router.get(
'/:username/full-stack-certification',
showCert
);
router.get('/:username', returnUser);
app.use(router);
@ -184,14 +211,20 @@ module.exports = function(app) {
return (obj.name || '').match(/^Waypoint/i);
});
debug('user is fec', profileUser.isFrontEndCert);
res.render('account/show', {
title: 'Camper ' + profileUser.username + '\'s portfolio',
username: profileUser.username,
name: profileUser.name,
isMigrationGrandfathered: profileUser.isMigrationGrandfathered,
isGithubCool: profileUser.isGithubCool,
isLocked: !!profileUser.isLocked,
isFrontEndCert: profileUser.isFrontEndCert,
isFullStackCert: profileUser.isFullStackCert,
isHonest: profileUser.isHonest,
location: profileUser.location,
calender: data,
@ -216,6 +249,92 @@ module.exports = function(app) {
);
}
function showCert(req, res, next) {
const username = req.params.username.toLowerCase();
const { user } = req;
const showFront = req.path.split('/').pop() === 'front-end-certification';
Observable.just(user)
.flatMap(user => {
if (user && user.username === username) {
return Observable.just(user);
}
return findUserByUsername$(username, {
isFrontEndCert: true,
isFullStackCert: true,
completedChallenges: true,
username: true,
name: true
});
})
.subscribe(
(user) => {
if (!user) {
req.flash('errors', {
msg: `404: We couldn't find the user ${username}`
});
return res.redirect('/');
}
if (!user.isGithubCool) {
req.flash('errors', {
msg: dedent`
This user needs to link GitHub with their account
in order to display this certificate to the public.
`
});
return res.redirect('back');
}
if (user.isLocked) {
req.flash('errors', {
msg: dedent`
${username} has chosen to hide their work from the public.
They need to unhide their work in order for this certificate to
be verifiable.
`
});
return res.redirect('back');
}
if (!user.isHonest) {
req.flash('errors', {
msg: dedent`
${username} has not agreed to our Academic Honesty Pledge yet.
`
});
return res.redirect('back');
}
if (
showFront && user.isFrontEndCert ||
!showFront && user.isFullStackCert
) {
var { completedDate } = _.find(user.completedChallenges, {
id: showFront ?
frontEndChallangeId :
fullStackChallangeId
});
return res.render(
showFront ?
'certificate/front-end.jade' :
'certificate/full-stack.jade',
{
username: user.username,
date: moment(new Date(completedDate))
.format('MMMM, Do YYYY'),
name: user.name
}
);
}
req.flash('errors', {
msg: showFront ?
`Looks like user ${username} is not Front End certified` :
`Looks like user ${username} is not Full Stack certified`
});
res.redirect('/map');
},
next
);
}
function toggleLockdownMode(req, res, next) {
if (req.user.isLocked === true) {
req.user.isLocked = false;
@ -297,11 +416,6 @@ module.exports = function(app) {
});
}
/**
* POST /forgot
* Create a random token, then the send user an email with a reset link.
*/
function postForgot(req, res) {
const errors = req.validationErrors();
const email = req.body.email.toLowerCase();

View File

@ -1,3 +1,5 @@
{
"gitHubUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1521.3 Safari/537.36"
"gitHubUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1521.3 Safari/537.36",
"frontEndChallangeId": "561add10cb82ac38a17513be",
"fullStackChallangeId": "660add10cb82ac38a17513be"
}

View File

@ -1,60 +1,24 @@
var R = require('ramda');
/*
* Middleware to migrate users from fragmented challenge structure to unified
* challenge structure
*
* @param req
* @param res
* @returns null
*/
exports.userMigration = function userMigration(req, res, next) {
if (!req.user || req.user.completedChallenges.length !== 0) {
return next();
}
req.user.completedChallenges = R.filter(function(elem) {
// getting rid of undefined
return elem;
}, R.concat(
req.user.completedCoursewares,
req.user.completedBonfires.map(function(bonfire) {
return ({
completedDate: bonfire.completedDate,
id: bonfire.id,
name: bonfire.name,
completedWith: bonfire.completedWith,
solution: bonfire.solution,
githubLink: '',
verified: false,
challengeType: 5
});
})
)
);
return next();
};
exports.ifNoUserRedirectTo = function ifNoUserRedirectTo(url) {
export function ifNoUserRedirectTo(url) {
return function(req, res, next) {
if (req.user) {
return next();
}
return res.redirect(url);
};
};
}
exports.ifNoUserSend = function ifNoUserSend(sendThis) {
export function ifNoUserSend(sendThis) {
return function(req, res, next) {
if (req.user) {
return next();
}
return res.status(200).send(sendThis);
};
};
}
exports.ifNoUser401 = function ifNoUser401(req, res, next) {
export function ifNoUser401(req, res, next) {
if (req.user) {
return next();
}
return res.status(401).end();
};
}

View File

@ -1,7 +1,9 @@
var Rx = require('rx');
var debug = require('debug')('freecc:rxUtils');
import Rx from 'rx';
import debugFactory from 'debug';
exports.saveInstance = function saveInstance(instance) {
const debug = debugFactory('freecc:rxUtils');
export function saveInstance(instance) {
return new Rx.Observable.create(function(observer) {
if (!instance || typeof instance.save !== 'function') {
debug('no instance or save method');
@ -17,16 +19,15 @@ exports.saveInstance = function saveInstance(instance) {
observer.onCompleted();
});
});
};
}
// alias saveInstance
exports.saveUser = exports.saveInstance;
export const saveUser = saveInstance;
exports.observeQuery = exports.observableQueryFromModel =
function observableQueryFromModel(Model, method, query) {
export function observeQuery(Model, method, query) {
return Rx.Observable.fromNodeCallback(Model[method], Model)(query);
};
}
exports.observeMethod = function observeMethod(context, methodName) {
export function observeMethod(context, methodName) {
return Rx.Observable.fromNodeCallback(context[methodName], context);
};
}

View File

@ -58,8 +58,13 @@ block content
h1.flat-top.wrappable= name
h1.flat-top.wrappable= location
h1.flat-top.text-primary= "[ " + (progressTimestamps.length) + " ]"
if isFrontEndCert
a.btn.btn-primary(href='/' + username + '/front-end-certification') View My Front End Development Certification
if isFullStackCert
.button-spacer
a.btn.btn-success(href='/' + username + '/full-stack-certification') View My Full Stack Development Certification
if (user && user.username !== username)
a.btn.btn-lg.btn-block.btn-twitter.btn-link-social(href='/link/twitter')
a.btn.btn-lg.btn-block.btn-twitter.btn-link-social(href='/leaderboard/add?username=#{username}')
i.fa.fa-plus-square
| Add them to my personal leaderboard

View File

@ -0,0 +1,45 @@
style.
@font-face {
font-family: "Sax Mono";
src: url("/fonts/saxmono.ttf") format("truetype");
}
body {
display: inline-block;
font-family: "Sax Mono", monospace;
margin: 0;
position: absolute;
text-align: center;
}
.img-abs {
left 0;
position: relative;
top: 0;
width: 2000px
}
.cert-name {
font-size: 64px;
left: 1000px;
position: absolute;
top: 704px;
z-index: 1000;
}
.cert-date {
font-size: 60px;
left: 760px;
position: absolute;
top: 1004.8px;
z-index: 1000;
}
.cert-link {
font-size: 22px;
left: 120px;
position: absolute;
top: 1488px;
z-index: 1000;
}

View File

@ -0,0 +1,6 @@
include font
#name.cert-name= name
img#cert.img-abs(src='http://i.imgur.com/ToFZKBd.jpg')
.cert-date= date
.cert-link verify this certification at: http://freecodecamp.com/#{username}/front-end-certification
include script

View File

@ -0,0 +1,6 @@
include font
#name.cert-name= name
img#cert.img-abs(src='http://i.imgur.com/Z4PgjBQ.jpg')
.cert-date= date
.cert-link verify this certification at: http://freecodecamp.com/#{username}/full-stack-certification
include script

View File

@ -0,0 +1,7 @@
extends ../layout
block content
.panel.panel-info
.panel-heading.text-center
h1 Certificate
.panel-body
p foo

View File

@ -0,0 +1,8 @@
script.
(function() {
var containerWidth = document.getElementById('cert').offsetWidth;
var nameDiv = document.getElementById('name');
var nameWidth = nameDiv.offsetWidth;
console.log(containerWidth, nameWidth);
nameDiv.style.left = ((containerWidth - nameWidth) / 2) + 15;
})();

View File

@ -0,0 +1,54 @@
extends ../layout
block content
.panel.panel-info
.panel-body
h3.text-center Commit to yourself. Commit to a nonprofit.
.col-xs-12.col-sm-6.col-sm-offset-3
p Are you looking for a burst of motivation? Do you want to help nonprofits before youre ready to code for them? You can do both by pledging a monthly donation to a nonprofit until you've earned either your Front End or Full Stack Development certificate. Join Commit below or click "maybe later".
.col-xs-12.col-sm-6.col-sm-offset-3
h4 Step 1: Choose your goal
.radio
label
input(type='radio' id='front-end-development-certificate' name='goal')
| Front End Development Certificate (takes about 400 hours)
.radio
label
input(type='radio' id='full-stack-development-certificate' name='goal')
| Full Stack Development Certificate (takes about 800 hours)
.spacer
h4 Step 2: Choose one of our nonprofits
.row
.col-xs-12.col-sm-6
a(href="http://i.imgur.com/U1CyEuA.jpg" data-lightbox="img-enlarge")
img.img-responsive(src='http://i.imgur.com/U1CyEuA.jpg' alt="Girl Develop It participants coding at tables.")
.radio
label
input(type='radio' id='girl-develop-it' name='nonprofit')
| Girl Develop It is a nonprofit that provides in-person classes for women to learn to code.
.col-xs-12.col-sm-6
a(href="http://i.imgur.com/NERytFF.jpg" data-lightbox="img-enlarge")
img.img-responsive(src='http://i.imgur.com/NERytFF.jpg' alt="Vets in Tech participants standing together at a conference.")
.radio
label
input(type='radio' id='vets-in-tech' name='nonprofit')
| Vets in Tech is a nonprofit that helps veterans prepare for tech jobs.
.spacer
h4 Step 3: Choose your monthly pledge
.radio
label
input(type='radio' id='5-dollar-pledge' name='pledge-amount')
| $5 per month
.radio
label
input(type='radio' id='10-dollar-pledge' name='pledge-amount')
| $10 per month
.radio
label
input(type='radio' id='50-dollar-pledge' name='pledge-amount')
| $50 per month
.spacer
a.button.btn.btn-block.btn-primary(href='https://www.paypal.com/us/cgi-bin/webscr?cmd=_flow&SESSION=T3x0DY-bLMFXuhmjYZXs-BhmDoiXfuNh5BWad5VBcMomkkDSZY0b_-_W3HS&dispatch=5885d80a13c0db1f8e263663d3faee8d0b9dcb01a9b6dc564e45f62871326a5e') Commit
.button-spacer
a.button.btn.btn-block.btn-warning(href='/') Maybe later
.spacer

View File

@ -9,7 +9,7 @@ block content
.caption
p.large-p= step[2]
if step[3]
a.btn.btn-block.btn-primary.challenge-step-btn-action(href='#{step[3]}' target='_blank') Go To Link
a.btn.btn-block.btn-primary.challenge-step-btn-action(id='#{index}' href='#{step[3]}' target='_blank') Go To Link
if index + 1 === description.length
.btn.btn-block.btn-primary.challenge-step-btn-finish(id='last' class=step[3] ? 'disabled' : '') Finish challenge
else
@ -32,8 +32,12 @@ block content
a.btn.btn-lg.btn-primary.btn-block(href='/challenges/next-challenge?id=' + challengeId) Go to my next challenge
script(src=rev('/js', 'commonFramework.js'))
script.
var common = common || { init: [] };
var common = window.common || { init: [] };
common.challengeId = !{JSON.stringify(challengeId)};
common.challengeName = !{JSON.stringify(name)};
common.challengeType = 7;
common.dashedName = !{JSON.stringify(dashedName || '')};
common.isHonest = !{JSON.stringify(isHonest || false)};
common.isFrontEndCert = !{JSON.stringify(isFrontEndCert || false)};
common.isFullStackCert = !{JSON.stringify(isFullStackCert || false)};
common.challengeSeed = !{JSON.stringify(challengeSeed || [])};