Merge pull request #3570 from FreeCodeCamp/feature/certificates
Add certification page
This commit is contained in:
@ -865,14 +865,53 @@ common.init.push((function() {
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function handleActionClick() {
|
||||
$(this)
|
||||
.parent()
|
||||
.find('.disabled')
|
||||
.removeClass('disabled');
|
||||
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) {
|
||||
|
@ -102,14 +102,31 @@
|
||||
},
|
||||
"isLocked": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
"default": false,
|
||||
"description": "Campers profile does not show challenges 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
BIN
public/fonts/saxmono.ttf
Executable file
Binary file not shown.
@ -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 else’s 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.",
|
||||
""
|
||||
]
|
||||
],
|
||||
|
@ -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 else’s 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.",
|
||||
""
|
||||
]
|
||||
],
|
||||
|
139
server/boot/certificate.js
Normal file
139
server/boot/certificate.js
Normal file
@ -0,0 +1,139 @@
|
||||
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';
|
||||
|
||||
const frontEndChallangeId = '561add10cb82ac38a17513be';
|
||||
const fullStackChallangeId = '660add10cb82ac38a17513be';
|
||||
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,
|
||||
{
|
||||
tests: true
|
||||
}
|
||||
)
|
||||
.map(({ tests = [] }) => tests)
|
||||
.shareReplay();
|
||||
|
||||
const fullStackChallangeIds$ = observeQuery(
|
||||
Challenge,
|
||||
'findById',
|
||||
fullStackChallangeId,
|
||||
{
|
||||
tests: true
|
||||
}
|
||||
)
|
||||
.map(({ tests = [] }) => tests)
|
||||
.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((tests) => {
|
||||
const { user } = req;
|
||||
if (
|
||||
isFront && !user.isFrontEndCert && isCertified(tests, user) ||
|
||||
!isFront && !user.isFullStackCert && isCertified(tests, user)
|
||||
) {
|
||||
debug('certified');
|
||||
if (isFront) {
|
||||
user.isFrontEndCert = true;
|
||||
user.completedChallenges.push({
|
||||
completedDate: new Date(),
|
||||
id: frontEndChallangeId
|
||||
})
|
||||
} else {
|
||||
user.isFullStackCert = true;
|
||||
user.completedChallenges.push({
|
||||
completedDate: new Date(),
|
||||
id: fullStackChallangeId
|
||||
})
|
||||
}
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
@ -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',
|
||||
@ -330,7 +327,7 @@ module.exports = function(app) {
|
||||
challengeType: 5
|
||||
};
|
||||
|
||||
observableQueryFromModel(
|
||||
observeQuery(
|
||||
User,
|
||||
'findOne',
|
||||
{ where: { username: ('' + completedWith).toLowerCase() } }
|
||||
@ -458,7 +455,7 @@ module.exports = function(app) {
|
||||
verified: false
|
||||
};
|
||||
|
||||
observableQueryFromModel(
|
||||
observeQuery(
|
||||
User,
|
||||
'findOne',
|
||||
{ where: { username: completedWith.toLowerCase() } }
|
||||
|
@ -1,8 +1,11 @@
|
||||
import _ from 'lodash';
|
||||
import dedent from 'dedent';
|
||||
import moment from 'moment';
|
||||
import { Observable } from 'rx';
|
||||
import debugFactory from 'debug';
|
||||
|
||||
import { ifNoUser401, ifNoUserRedirectTo } from '../utils/middleware';
|
||||
import { observeQuery } from '../utils/rx';
|
||||
|
||||
const debug = debugFactory('freecc:boot:user');
|
||||
const daysBetween = 1.5;
|
||||
@ -52,7 +55,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 +97,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 +207,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 +245,64 @@ 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 (
|
||||
showFront && user.isFrontEndCert ||
|
||||
!showFront && user.isFullStackCert
|
||||
) {
|
||||
var { completedDate } = _.find(user.completedChallenges, {
|
||||
id: showFront ?
|
||||
'561add10cb82ac38a17513be' :
|
||||
'660add10cb82ac38a17513be'
|
||||
});
|
||||
|
||||
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 +384,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();
|
||||
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
@ -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) {
|
||||
return Rx.Observable.fromNodeCallback(Model[method], Model)(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);
|
||||
};
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
45
server/views/certificate/font.jade
Normal file
45
server/views/certificate/font.jade
Normal 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;
|
||||
}
|
||||
|
6
server/views/certificate/front-end.jade
Normal file
6
server/views/certificate/front-end.jade
Normal 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
|
6
server/views/certificate/full-stack.jade
Normal file
6
server/views/certificate/full-stack.jade
Normal 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
|
7
server/views/certificate/index.jade
Normal file
7
server/views/certificate/index.jade
Normal file
@ -0,0 +1,7 @@
|
||||
extends ../layout
|
||||
block content
|
||||
.panel.panel-info
|
||||
.panel-heading.text-center
|
||||
h1 Certificate
|
||||
.panel-body
|
||||
p foo
|
8
server/views/certificate/script.jade
Normal file
8
server/views/certificate/script.jade
Normal 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;
|
||||
})();
|
@ -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 || [])};
|
||||
|
Reference in New Issue
Block a user