Merge branch 'master' into passwords

Conflicts:
	static/media/css/core.css
	static/media/less/core.less
This commit is contained in:
Andrew Hayward 2013-05-30 01:21:40 +01:00
Родитель eecc1a038d 665e3eef1b
Коммит 07b9e2b37c
25 изменённых файлов: 891 добавлений и 240 удалений

Просмотреть файл

@ -11,5 +11,7 @@ Property | Default | Description
`CSOL_DB_PASS` | `null` | Database password.
`CSOL_DB_HOST` | `null` | Database host.
`CSOL_DB_PORT` | `null` | Database port.
`CSOL_HOST` | `null` | Canonical CSOL host (eg chicagosummeroflearning.org)
`COOKIE_SECRET` | `null` | Seed for session cookie.
`CSOL_OPENBADGER_URL` | `null` | Openbadger API Location, http://obr.com/v2/
`CSOL_OPENBADGER_SECRET` | `null` | A shared secret with Open Badger. Should match the OPENBADGER_JWT_SECRET variable on open badger

36
api.js
Просмотреть файл

@ -85,15 +85,18 @@ function getFullUrl(origin, path) {
}
// Load data from remote endpoint
function remote (method, path, callback) {
if (!request[method])
return callback(new errors.NotImplemented('Unknown method ' + method));
// TODO - need to add ability to pass data through
// TODO - might want to cache this at some point
function remote (method, path, options, callback) {
if (!request[method])
return callback(new errors.NotImplemented('Unknown method ' + method));
if (_.isFunction(options)) {
callback = options;
options = {};
}
var endpointUrl = getFullUrl(this.origin, path);
request[method](endpointUrl, function(err, response, body) {
request[method](endpointUrl, options, function(err, response, body) {
logger.log('info', 'API request: "%s %s" %s',
method.toUpperCase(), endpointUrl, response ? response.statusCode : "Error", err);
@ -101,12 +104,17 @@ function remote (method, path, callback) {
if (err)
return callback(new errors.Unknown(err));
if (response.statusCode !== 200)
// TODO - add logging so the upstream error can be debugged
return callback(new (errors.lookup(response.statusCode))());
if (response.statusCode !== 200) {
var msg;
if (body && body.reason)
msg = body.reason;
return callback(new (errors.lookup(response.statusCode))(msg));
}
try {
var data = JSON.parse(body);
var data = body;
if (!_.isObject(body))
data = JSON.parse(data);
} catch (e) {
return callback(new errors.Unknown(e.message));
}
@ -170,10 +178,12 @@ module.exports = function Api(origin, config) {
_.each(['get', 'post', 'put', 'patch', 'head', 'del'], function(method) {
Object.defineProperty(this, method, {
enumerable: true,
value: function(path, callback) {
this.remote(method, path, callback);
value: function(path, opts, callback) {
this.remote(method, path, opts, callback);
},
writable: true // This is needed for mocking
/* TODO: writable is set to true for mocking, but it would
be nice to revisit and try to remove that line. */
writable: true
});
}, this);

Просмотреть файл

@ -2,6 +2,8 @@ var bcrypt = require('bcrypt');
var passwords = require('../lib/passwords');
var usernames = require('../lib/usernames');
var db = require('../db');
var email = require('../mandrill');
var logger = require('../logger');
var learners = db.model('Learner');
var guardians = db.model('Guardian');
var signupTokens = db.model('SignupToken');
@ -9,6 +11,10 @@ var passwordTokens = db.model('PasswordToken');
var COPPA_MAX_AGE = process.env.COPPA_MAX_AGE || 13;
var BCRYPT_SEED_ROUNDS = process.env.BCRYPT_SEED_ROUNDS || 10;
var CSOL_HOST = process.env.CSOL_HOST;
if (!CSOL_HOST)
throw new Error('Must specify CSOL_HOST in the environment');
function validateEmail (email) {
// TODO - make sure email is valid
@ -69,13 +75,19 @@ function extractUserData (user) {
type: userType,
favorites: [],
dependents: [],
home: userHome
home: userHome,
underage: user.underage
};
}
function redirectUser (req, res, user, status) {
req.session.user = extractUserData(user);
return res.redirect(status || 303, req.session.user.home);
var target = req.session.user.home;
if (req.session.afterLogin) {
target = req.session.afterLogin;
delete req.session.afterLogin;
}
return res.redirect(status || 303, target);
}
function clearUser (req, res) {
@ -166,8 +178,6 @@ function processChildLearnerSignup (req, res, next) {
}).complete(function(err, token) {
if (err || !token) return fail(err);
// TODO - send an email
token.setLearner(user); // Assuming this worked
bcrypt.hash(signup.password, BCRYPT_SEED_ROUNDS, function(err, hash) {
@ -175,10 +185,16 @@ function processChildLearnerSignup (req, res, next) {
user.updateAttributes({
complete: true,
password: hash
password: hash,
email: normalizedUsername + '@' + CSOL_HOST
}).complete(function(err) {
if (err) return fail(err);
var confirmationUrl = req.protocol + '://' + req.get('Host')
+ '/signup/' + token.token;
email.send('<13 learner signup', {
confirmationUrl: confirmationUrl
}, signup.parent_email);
delete req.session.signup;
req.flash('modal', {
title: 'Welcome to the Chicago Summer of Learning',
@ -240,6 +256,7 @@ function processStandardLearnerSignup (req, res, next) {
return fail(err);
}
email.send('learner signup', {}, signup.email);
delete req.session.signup;
redirectUser(req, res, user);
});

Просмотреть файл

@ -1,11 +1,37 @@
const openbadger = require('../openbadger');
module.exports = function (app) {
app.get('/claim', function (req, res, next) {
res.render('claim.html');
var claimCode = req.query.code;
var user = res.locals.user;
if (!user) {
req.session.afterLogin = req.originalUrl;
return res.redirect('/login');
}
if (!claimCode)
return res.render('claim.html');
openbadger.claim({
code: claimCode.trim(),
email: user.email
}, function(err, data) {
if (err) {
if (err.code === 404 && err.message === 'unknown claim code')
req.flash('error', "That claim code appears to be invalid.");
else if (err.code === 409)
req.flash('warn', "You already have that badge.");
else
req.flash('error', "Unable to claim badge.");
}
else {
req.flash('success', 'Badge claimed!');
}
return res.redirect('/backpack');
});
app.get('/claim/:badgeName', function (req, res, next) {
return res.redirect('/badges/'+req.params.badgeName+'/claim');
});
app.get('/backpack', function (req, res, next) {

Просмотреть файл

@ -89,6 +89,7 @@ var BadRequest = createExceptionType('BadRequest', 400);
var Unauthorized = createExceptionType('Unauthorized', 401);
var Forbidden = createExceptionType('Forbidden', 403);
var NotFound = createExceptionType('NotFound', 404);
var Conflict = createExceptionType('Conflict', 409);
var Unknown = createExceptionType('Internal', 500);
var NotImplemented = createExceptionType('NotImplemented', 501);
var BadGateway = createExceptionType('BadGateway', 502);

Просмотреть файл

@ -7,7 +7,7 @@ const FAKE_EMAIL = ('DEBUG' in process.env)
var request = require('request');
if (FAKE_EMAIL) {
request = function(opts, cb) {
request.post = function(opts, cb) {
logger.log('debug', 'FAKE EMAIL: request.post with opts', opts);
cb('EMAIL DISABLED');
};
@ -18,7 +18,8 @@ const ENDPOINT = process.env['CSOL_MANDRILL_URL'] ||
const KEY = process.env['CSOL_MANDRILL_KEY'];
const TEMPLATES = {
test: 'test'
'<13 learner signup': 'csol-13-signup',
'learner signup': 'csol-signup'
}
module.exports = {
@ -26,7 +27,8 @@ module.exports = {
/*
send(template, context, recipient, callback)
template - internal template name, mapped to mandrill names above
template - internal template name, mapped to mandrill names above, or
mandrill template name
context - merge variables (optional)
{ foo: 'hi' } replaces *|foo|* or *|FOO|*
in the template with "hi"
@ -66,7 +68,7 @@ module.exports = {
var payload = {
key: KEY,
template_name: template,
template_name: TEMPLATES[template] || template,
template_content: [],
message: {
to: recipients,
@ -89,6 +91,21 @@ module.exports = {
if (response.statusCode !== 200)
return callback(body);
var unsent = [];
_.map(body, function(result) {
var level = 'info';
if (['sent', 'queued'].indexOf(result.status) === -1) {
level = 'error';
unsent.push(result);
}
logger.log(level, 'Learner signup email %s for %s', result.status, result.email);
});
if (unsent.length)
return callback({
message: 'Some addresses not sent or queued',
results: unsent
});
return callback(null, body);
});
}

Просмотреть файл

@ -1,10 +1,16 @@
const Api = require('./api');
const errors = require('./lib/errors');
const _ = require('underscore');
const jwt = require('jwt-simple');
const ENDPOINT = process.env['CSOL_OPENBADGER_URL'];
const JWT_SECRET = process.env['CSOL_OPENBADGER_SECRET'];
const TOKEN_LIFETIME = process.env['CSOL_OPENBADGER_TOKEN_LIFETIME'] || 10000;
if (!ENDPOINT)
throw new Error('Must specify CSOL_OPENBADGER_URL in the environment');
if (!JWT_SECRET)
throw new Error('Must specify CSOL_OPENBADGER_SECRET in the environment');
function normalizeBadge (badge, id) {
if (!id)
@ -105,6 +111,24 @@ var openbadger = new Api(ENDPOINT, {
orgs: _.values(data.issuers)
});
});
},
claim: function claim (query, callback) {
var email = query.email;
var code = query.code;
var claims = {
prn: email,
exp: Date.now() + TOKEN_LIFETIME
};
var token = jwt.encode(claims, JWT_SECRET);
var params = {
auth: token,
email: email,
code: code,
};
this.post('/claim', { json: params }, function(err, data) {
return callback(err, data);
});
}
});

Просмотреть файл

@ -14,7 +14,8 @@
"sequelize": "~1.6.0",
"tap": "~0.4.1",
"underscore": "~1.4.4",
"winston": "~0.7.1"
"winston": "~0.7.1",
"jwt-simple": "~0.1.0"
},
"devDependencies": {
"sinon": "~1.7.2",

Просмотреть файл

@ -1,7 +1,11 @@
@import url(http://fonts.googleapis.com/css?family=Open+Sans:400,300);
/*general*/
html,
body {
height: 100%;
font-family: 'Open Sans', sans-serif;
font-weight: 300;
line-height: 18px;
}
body {
margin: 0px;
@ -43,6 +47,10 @@ ol {
.navbar .nav {
float: none;
}
.navbar .nav > li.dropdown.open > .dropdown-toggle {
background-color: inherit;
text-decoration: underline;
}
.navbar .nav > li > a {
border-left: none;
border-right: none;
@ -71,7 +79,7 @@ ol {
-webkit-box-shadow: inset 0px -10px 20px 0px rgba(0, 0, 0, 0.25);
-moz-box-shadow: inset 0px -10px 20px 0px rgba(0, 0, 0, 0.25);
box-shadow: inset 0px -10px 20px 0px rgba(0, 0, 0, 0.25);
padding-top: 1em;
padding-top: 25px;
}
.navbar .nav-wrap {
width: 100%;
@ -145,11 +153,10 @@ ol {
color: #c0c0c0;
text-decoration: none;
}
.footer .lower {
border-top: 1px solid white;
padding-top: 20px;
.footer .upper {
padding: 50px 0 0 0;
}
.footer .lower p {
.footer .upper p {
background-image: url('../img/csol_logo_sm.png');
padding-left: 209px;
background-repeat: no-repeat;
@ -158,12 +165,13 @@ ol {
min-height: 131px;
margin-bottom: 20px;
}
.footer .lower li {
.footer .upper li {
border-left: none;
border-right: none;
}
.footer .upper {
padding: 1em 0;
.footer .lower {
border-top: 1px solid white;
padding: 25px 0 0 0;
}
.footer ul li {
display: inline-block;
@ -199,9 +207,18 @@ ol {
padding: 0px 0px 250px 0px;
}
.wrapper.secondary {
position: relative;
color: #fff;
height: 250px;
background-image: url('../img/chalkboard_bg.jpg');
background-image: url('../img/background-chalkboard-green.jpg');
background-repeat: no-repeat;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/background-chalkboard-green.jpg', sizingMethod='scale');
-ms-filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/background-chalkboard-green.jpg', sizingMethod='scale')";
overflow: visible;
}
/*CSOL-site specific*/
.poster a,
@ -248,3 +265,186 @@ input[type="password"].metered:focus:invalid + .password-meter {
input[type="password"].metered:focus:invalid:focus + .password-meter {
border-color: #e9322d;
}
/*landing page specific*/
body.home {
background-image: url('../img/chalkboard_bg.jpg');
}
body.home .container {
width: 960px;
}
body.home .navbar .navbar-inner {
background-image: url('../img/spacedimg.jpg');
background-repeat: no-repeat;
background-position: top center;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
padding-top: 0px;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/spacedimg.png', sizingMethod='scale');
-ms-filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/spacedimg.png', sizingMethod='scale')";
overflow: visible;
max-height: 515px;
}
body.home .navbar .navbar-inner > .container {
background-image: url('../img/home_drawing.png');
background-position: top center;
background-repeat: no-repeat;
}
body.home .navbar .brand {
background-image: url('../img/csol_logo_sm.png');
position: absolute;
left: 50%;
margin-left: -102px;
z-index: 2;
top: 50px;
width: 189px;
height: 131px;
padding: 0 0 0 0;
}
body.home .navbar .nav-wrap {
width: inherit;
margin-bottom: 0px;
height: 600px;
width: 310px;
margin: 0 auto;
background: none;
background-image: url('../img/banner300.png');
background-position: top center;
background-repeat: no-repeat;
position: relative;
z-index: 1;
box-shadow: none;
}
body.home .navbar .nav-wrap ul {
padding-top: 200px;
left: -6px;
}
body.home .navbar .nav-wrap ul > li {
text-align: left;
display: block;
line-height: 9px;
}
body.home .navbar .nav-wrap ul > li > a {
text-transform: inherit;
font-size: 22px;
color: #f4fc00;
text-transform: lowercase;
}
body.home .navbar .nav-wrap ul > li.learn > a:first-letter {
text-transform: capitalize;
}
body.home .navbar .nav-wrap ul > li.log-in > a {
font-size: 18px;
color: #fff;
text-transform: lowercase;
text-align: center;
line-height: 24px;
}
body.home .navbar .nav-wrap ul > li.video {
margin-top: 35px;
}
body.home .navbar .nav-wrap ul > li.video a {
color: #fff;
border-radius: 7px;
margin: auto;
display: block;
width: 140px;
text-align: center;
line-height: 20px;
background: #e82202;
background: -moz-linear-gradient(top, #e82202 0%, #e5381d 44%, #e56854 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #e82202), color-stop(44%, #e5381d), color-stop(100%, #e56854));
background: -webkit-linear-gradient(top, #e82202 0%, #e5381d 44%, #e56854 100%);
background: -o-linear-gradient(top, #e82202 0%, #e5381d 44%, #e56854 100%);
background: -ms-linear-gradient(top, #e82202 0%, #e5381d 44%, #e56854 100%);
background: linear-gradient(to bottom, #e82202 0%, #e5381d 44%, #e56854 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#e82202', endColorstr='#e56854', GradientType=0);
}
body.home .navbar .nav-wrap ul > li span {
color: #fff;
}
body.home .navbar .nav-wrap ul > li:nth-child(even) {
border-left: none;
border-right: none;
}
body.home #main {
padding: 0 0 0 0;
display: none;
}
body.home .navbar-static-top .container {
width: inherit;
}
body.home .secondary .upper {
padding-top: 15px;
position: relative;
}
body.home .secondary .upper > p.pull-left {
display: none;
}
body.home .secondary .upper > .pull-right {
float: left;
text-align: center;
width: 340px;
padding-top: 55px;
}
body.home .secondary .lower {
border-top: none;
}
body.home .secondary .lower .pull-right li:first-child {
display: none;
}
body.home .secondary .lower .pull-right li:nth-child(3) {
border-left: 1px solid #fff;
border-right: 1px solid #fff;
}
body.home .secondary .lower .pull-left li:first-child {
border-right: 1px solid #fff;
}
body.home .footer {
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
body.home #rahm {
position: relative;
width: 210px;
padding-left: 100px;
padding-top: 15px;
background-image: url('../img/rahm.png');
background-repeat: no-repeat;
background-position: center left;
float: left;
-webkit-hyphens: none;
-moz-hyphens: none;
hyphens: none;
}
body.home #bubbles {
float: left;
background-image: url('../img/bubbles.png');
width: 310px;
height: 140px;
background-repeat: no-repeat;
background-position: 18px 0px;
margin-top: 15px;
}
body.home #bubbles span {
color: #000;
display: block;
width: 100px;
}
body.home #bubbles span.lt {
float: left;
margin-left: 34px;
margin-top: 12px;
}
body.home #bubbles span.lt a {
color: #3B5998;
}
body.home #bubbles span.rt {
margin-top: 52px;
margin-right: 7px;
float: right;
font-size: 24px;
line-height: 20px;
}

36
static/media/css/core.min.css поставляемый
Просмотреть файл

@ -1,15 +1,17 @@
html,body{height:100%;}
@import url(http://fonts.googleapis.com/css?family=Open+Sans:400,300);
html,body{height:100%;font-family:'Open Sans',sans-serif;font-weight:300;line-height:18px;}
body{margin:0px;padding:0px;}body .container>br{display:none;}
#main{padding-top:45px;padding-bottom:25px;}#main>h1:first-child,#main>h2:first-child,#main>h3:first-child,#main>h4:first-child,#main>p:first-child{margin-top:0px;padding-top:0px;}
#main>div>h1:first-child,#main>div>h2:first-child,#main>div>h3:first-child,#main>div>h4:first-child,#main>div>p:first-child{margin-top:0px;padding-top:0px;}
ul,ol{padding:0 0 0 0;margin:0 0 0 0;}
.row,[class*="span"]{margin-left:0px;}
.navbar .nav{float:none;}.navbar .nav>li>a{border-left:none;border-right:none;}
.navbar .nav{float:none;}.navbar .nav>li.dropdown.open>.dropdown-toggle{background-color:inherit;text-decoration:underline;}
.navbar .nav>li>a{border-left:none;border-right:none;}
.navbar .nav>li>a:hover,.navbar .nav>li>a:focus{background-color:inherit;border-left:none;border-right:none;}
.navbar .nav>li:nth-child(even){border-left:1px solid #c0c0c0;border-right:1px solid #c0c0c0;}
.navbar .nav>.navbar .nav>li:last-child{border-right:none;}
.navbar .nav .active a,.navbar .nav .active a:hover,.navbar .nav .active a:focus{background-color:white;box-shadow:none;}
.navbar .navbar-inner{background-image:url('../img/chalkboard_bg.jpg');-webkit-box-shadow:inset 0px -10px 20px 0px rgba(0, 0, 0, 0.25);-moz-box-shadow:inset 0px -10px 20px 0px rgba(0, 0, 0, 0.25);box-shadow:inset 0px -10px 20px 0px rgba(0, 0, 0, 0.25);padding-top:1em;}
.navbar .navbar-inner{background-image:url('../img/chalkboard_bg.jpg');-webkit-box-shadow:inset 0px -10px 20px 0px rgba(0, 0, 0, 0.25);-moz-box-shadow:inset 0px -10px 20px 0px rgba(0, 0, 0, 0.25);box-shadow:inset 0px -10px 20px 0px rgba(0, 0, 0, 0.25);padding-top:25px;}
.navbar .nav-wrap{width:100%;padding:0 0 0 0;margin:0 0 0 0;width:940px;margin-bottom:-24px;-webkit-box-shadow:0 0 8px 3px rgba(0, 0, 0, 0.25);-moz-box-shadow:0 0 8px 3px rgba(0, 0, 0, 0.25);box-shadow:0 0 8px 3px rgba(0, 0, 0, 0.25);background:white;text-align:center;}.navbar .nav-wrap ul{display:inline-block;margin:0 0 0 0;}.navbar .nav-wrap ul>li{float:none;display:inline-block;}.navbar .nav-wrap ul>li>a{text-shadow:none;color:#333333;text-transform:uppercase;}
.navbar .nav-wrap ul>li>a:focus,.navbar .nav-wrap ul>li>a:hover{color:#c0c0c0;}
.navbar .nav-wrap ul>li:last-child{border-right:none;}
@ -20,16 +22,16 @@ ul,ol{padding:0 0 0 0;margin:0 0 0 0;}
.footer a.logo.mac{background-image:url('../img/mac.png');}
.footer a.logo.moz{background-image:url('../img/moz.png');}
.footer a:hover{color:#c0c0c0;text-decoration:none;}
.footer .lower{border-top:1px solid white;padding-top:20px;}.footer .lower p{background-image:url('../img/csol_logo_sm.png');padding-left:209px;background-repeat:no-repeat;background-position:top left;width:400px;min-height:131px;margin-bottom:20px;}
.footer .lower li{border-left:none;border-right:none;}
.footer .upper{padding:1em 0;}
.footer .upper{padding:50px 0 0 0;}.footer .upper p{background-image:url('../img/csol_logo_sm.png');padding-left:209px;background-repeat:no-repeat;background-position:top left;width:400px;min-height:131px;margin-bottom:20px;}
.footer .upper li{border-left:none;border-right:none;}
.footer .lower{border-top:1px solid white;padding:25px 0 0 0;}
.footer ul li{display:inline-block;}.footer ul li>a,.footer ul li>span{margin:10px;}
.footer ul li:first-child a{margin-left:0px;}
.footer ul li.nth-child(even){border-left:1px solid #fff;border-right:1px solid #fff;}
.footer ul li.last-child{border-right:none;}
.wrapper{width:100%;}.wrapper .inner-wrapper{width:100%;}
.wrapper.primary{min-height:100%;height:auto !important;height:100%;margin:0px 0px -250px 0px;}.wrapper.primary .inner-wrapper{padding:0px 0px 250px 0px;}
.wrapper.secondary{color:#fff;height:250px;background-image:url('../img/chalkboard_bg.jpg');}
.wrapper.secondary{position:relative;color:#fff;height:250px;background-image:url('../img/background-chalkboard-green.jpg');background-repeat:no-repeat;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/background-chalkboard-green.jpg', sizingMethod='scale');-ms-filter:"progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/background-chalkboard-green.jpg', sizingMethod='scale')";overflow:visible;}
.poster a,.poster img{display:block;margin-left:auto;margin-right:auto;}
#menu-login-form{padding:10px;text-align:left;}
input[type="password"].metered{padding-bottom:9px;}input[type="password"].metered+.password-meter{border:solid 1px #CCC;border-top:none;height:6px;margin:-6px 0 10px;-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;-moz-box-sizing:border-box;box-sizing:border-box;position:relative;overflow:hidden;-webkit-transition:border linear .2s;-moz-transition:border linear .2s;-o-transition:border linear .2s;transition:border linear .2s;}
@ -37,3 +39,23 @@ input[type="password"].metered+.password-meter .bar{border-radius:0 0 0 3px;over
input[type="password"].metered:focus+.password-meter{border-color:rgba(82, 168, 236, 0.8);}
input[type="password"].metered:focus:invalid+.password-meter{border-color:#ee5f5b;}
input[type="password"].metered:focus:invalid:focus+.password-meter{border-color:#e9322d;}
body.home{background-image:url('../img/chalkboard_bg.jpg');}body.home .container{width:960px;}
body.home .navbar .navbar-inner{background-image:url('../img/spacedimg.jpg');background-repeat:no-repeat;background-position:top center;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;padding-top:0px;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/spacedimg.png', sizingMethod='scale');-ms-filter:"progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/spacedimg.png', sizingMethod='scale')";overflow:visible;max-height:515px;}body.home .navbar .navbar-inner>.container{background-image:url('../img/home_drawing.png');background-position:top center;background-repeat:no-repeat;}
body.home .navbar .brand{background-image:url('../img/csol_logo_sm.png');position:absolute;left:50%;margin-left:-102px;z-index:2;top:50px;width:189px;height:131px;padding:0 0 0 0;}
body.home .navbar .nav-wrap{width:inherit;margin-bottom:0px;height:600px;width:310px;margin:0 auto;background:none;background-image:url('../img/banner300.png');background-position:top center;background-repeat:no-repeat;position:relative;z-index:1;box-shadow:none;}body.home .navbar .nav-wrap ul{padding-top:200px;left:-6px;}body.home .navbar .nav-wrap ul>li{text-align:left;display:block;line-height:9px;}body.home .navbar .nav-wrap ul>li>a{text-transform:inherit;font-size:22px;color:#f4fc00;text-transform:lowercase;}
body.home .navbar .nav-wrap ul>li.learn>a:first-letter{text-transform:capitalize;}
body.home .navbar .nav-wrap ul>li.log-in>a{font-size:18px;color:#fff;text-transform:lowercase;text-align:center;line-height:24px;}
body.home .navbar .nav-wrap ul>li.video{margin-top:35px;}body.home .navbar .nav-wrap ul>li.video a{color:#fff;border-radius:7px;margin:auto;display:block;width:140px;text-align:center;line-height:20px;background:#e82202;background:-moz-linear-gradient(top, #e82202 0%, #e5381d 44%, #e56854 100%);background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #e82202), color-stop(44%, #e5381d), color-stop(100%, #e56854));background:-webkit-linear-gradient(top, #e82202 0%, #e5381d 44%, #e56854 100%);background:-o-linear-gradient(top, #e82202 0%, #e5381d 44%, #e56854 100%);background:-ms-linear-gradient(top, #e82202 0%, #e5381d 44%, #e56854 100%);background:linear-gradient(to bottom, #e82202 0%, #e5381d 44%, #e56854 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e82202', endColorstr='#e56854', GradientType=0);}
body.home .navbar .nav-wrap ul>li span{color:#fff;}
body.home .navbar .nav-wrap ul>li:nth-child(even){border-left:none;border-right:none;}
body.home #main{padding:0 0 0 0;display:none;}
body.home .navbar-static-top .container{width:inherit;}
body.home .secondary .upper{padding-top:15px;position:relative;}body.home .secondary .upper>p.pull-left{display:none;}
body.home .secondary .upper>.pull-right{float:left;text-align:center;width:340px;padding-top:55px;}
body.home .secondary .lower{border-top:none;}body.home .secondary .lower .pull-right li:first-child{display:none;}
body.home .secondary .lower .pull-right li:nth-child(3){border-left:1px solid #fff;border-right:1px solid #fff;}
body.home .secondary .lower .pull-left li:first-child{border-right:1px solid #fff;}
body.home .footer{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
body.home #rahm{position:relative;width:210px;padding-left:100px;padding-top:15px;background-image:url('../img/rahm.png');background-repeat:no-repeat;background-position:center left;float:left;-webkit-hyphens:none;-moz-hyphens:none;hyphens:none;}
body.home #bubbles{float:left;background-image:url('../img/bubbles.png');width:310px;height:140px;background-repeat:no-repeat;background-position:18px 0px;margin-top:15px;}body.home #bubbles span{color:#000;display:block;width:100px;}body.home #bubbles span.lt{float:left;margin-left:34px;margin-top:12px;}body.home #bubbles span.lt a{color:#3B5998;}
body.home #bubbles span.rt{margin-top:52px;margin-right:7px;float:right;font-size:24px;line-height:20px;}

Двоичные данные
static/media/img/background-chalkboard-green.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 41 KiB

Двоичные данные
static/media/img/banner275.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 17 KiB

Двоичные данные
static/media/img/banner300.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 16 KiB

Двоичные данные
static/media/img/bubbles.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.4 KiB

Двоичные данные
static/media/img/home_drawing.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 42 KiB

Двоичные данные
static/media/img/rahm.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 12 KiB

Двоичные данные
static/media/img/spacedimg.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 133 KiB

Просмотреть файл

@ -1,3 +1,12 @@
$(document).ready(function(){
$('.show-tooltip').tooltip();
if($('body.home').length != 0) {
$('<p id="rahm">This summer Mayor Rahm Emanuel is challenging all Chicago youth to participate in the Summer of Learning. School stops for the summer, but learning never should.</p>').prependTo('.footer .upper');
$('<div id="bubbles"><span class="lt">join the conversation on <a href="#">Facebook</a>.</span><span class="rt">share stories</span></div>').appendTo('.footer .upper');
$('li.challenges').after('<li class="video"><a href="http://www.youtube.com/watch?v=6WwpwtYNsNk&feature=player_embedded" target=_blank>watch video</a></li>');
$('li.learn a').append('<span> your city</span>');
$('li.badges a').append('<span> Badges &</span>');
$('li.challenges a').append('<span> your future.</span>');
}
});

Просмотреть файл

@ -1,10 +1,14 @@
// Target: ../css/core.css
@import "mixins.less";
@import url(http://fonts.googleapis.com/css?family=Open+Sans:400,300);
/*general*/
html, body {
height:100%;
font-family:'Open Sans', sans-serif;
font-weight:300;
line-height:18px;
}
body {
margin:0px;
@ -41,6 +45,10 @@ ul, ol {
.nav {
float:none;
& > li {
&.dropdown.open > .dropdown-toggle {
background-color:inherit;
text-decoration:underline;
}
& > a {
border-left:none;
border-right:none;
@ -70,7 +78,7 @@ ul, ol {
-webkit-box-shadow:inset 0px -10px 20px 0px rgba(0, 0, 0, 0.25);
-moz-box-shadow:inset 0px -10px 20px 0px rgba(0, 0, 0, 0.25);
box-shadow:inset 0px -10px 20px 0px rgba(0, 0, 0, 0.25);
padding-top:1em;
padding-top:25px;
}
.nav-wrap {
width:100%;
@ -144,9 +152,8 @@ ul, ol {
color:#c0c0c0;
text-decoration:none;
}
.lower {
border-top:1px solid white;
padding-top:20px;
.upper {
padding:50px 0 0 0;
p {
background-image:url('../img/csol_logo_sm.png');
padding-left:209px;
@ -161,8 +168,9 @@ ul, ol {
border-right:none;
}
}
.upper {
padding:1em 0;
.lower {
border-top:1px solid white;
padding:25px 0 0 0;
}
ul {
li {
@ -202,9 +210,18 @@ ul, ol {
}
}
&.secondary {
position:relative;
color:#fff;
height:250px;
background-image:url('../img/chalkboard_bg.jpg');
background-image:url('../img/background-chalkboard-green.jpg');
background-repeat:no-repeat;
-webkit-background-size:cover;
-moz-background-size:cover;
-o-background-size:cover;
background-size:cover;
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/background-chalkboard-green.jpg', sizingMethod='scale');
-ms-filter:"progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/background-chalkboard-green.jpg', sizingMethod='scale')";
overflow:visible;
}
}
@ -259,3 +276,199 @@ input[type="password"].metered {
border-color: #e9322d;
}
}
/*landing page specific*/
body.home {
background-image:url('../img/chalkboard_bg.jpg');
.container {
width:960px;
}
.navbar {
.navbar-inner {
background-image:url('../img/spacedimg.jpg');
background-repeat:no-repeat;
background-position:top center;
-webkit-background-size:cover;
-moz-background-size:cover;
-o-background-size:cover;
background-size:cover;
padding-top:0px;
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/spacedimg.png', sizingMethod='scale');
-ms-filter:"progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/spacedimg.png', sizingMethod='scale')";
overflow:visible;
max-height:515px;
& > .container {
background-image:url('../img/home_drawing.png');
background-position:top center;
background-repeat:no-repeat;
}
}
.brand {
background-image:url('../img/csol_logo_sm.png');
position:absolute;
left:50%;
margin-left:-102px;
z-index:2;
top:50px;
width:189px;
height:131px;
padding:0 0 0 0;
}
.nav-wrap {
width:inherit;
margin-bottom:0px;
height:600px;
width:310px;
margin:0 auto;
background:none;
background-image:url('../img/banner300.png');
background-position:top center;
background-repeat:no-repeat;
position:relative;
z-index:1;
box-shadow:none;
ul {
padding-top:200px;
left:-6px;
& > li {
text-align:left;
display:block;
line-height:9px;
& > a {
text-transform:inherit;
font-size:22px;
color:#f4fc00;
text-transform:lowercase;
}
&.learn > a:first-letter {
text-transform:capitalize;
}
&.log-in > a {
font-size:18px;
color:#fff;
text-transform:lowercase;
text-align:center;
line-height:24px;
}
&.video {
margin-top:35px;
a {
color:#fff;
border-radius:7px;
margin:auto;
display:block;
width:140px;
text-align:center;
line-height:20px;
background:#e82202;
background:-moz-linear-gradient(top, #e82202 0%, #e5381d 44%, #e56854 100%);
background:-webkit-gradient(linear, left top, left bottom, color-stop(0%,#e82202), color-stop(44%,#e5381d), color-stop(100%,#e56854));
background:-webkit-linear-gradient(top, #e82202 0%,#e5381d 44%,#e56854 100%);
background:-o-linear-gradient(top, #e82202 0%,#e5381d 44%,#e56854 100%);
background:-ms-linear-gradient(top, #e82202 0%,#e5381d 44%,#e56854 100%);
background:linear-gradient(to bottom, #e82202 0%,#e5381d 44%,#e56854 100%);
filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#e82202', endColorstr='#e56854',GradientType=0 );
}
}
span {
color:#fff;
}
}
& > li:nth-child(even) {
border-left:none;
border-right:none;
}
}
}
}
#main {
padding:0 0 0 0;
display:none;
}
.navbar-static-top {
.container {
width:inherit;
}
}
.secondary {
.upper {
padding-top:15px;
position:relative;
> p.pull-left {
display:none;
}
> .pull-right {
float:left;
text-align:center;
width:340px;
padding-top:55px;
}
}
.lower {
border-top:none;
.pull-right {
li:first-child {
display:none;
}
li:nth-child(3) {
border-left:1px solid #fff;
border-right:1px solid #fff;
}
}
.pull-left {
li:first-child {
border-right:1px solid #fff;
}
}
}
}
.footer {
-webkit-box-shadow:none;
-moz-box-shadow:none;
box-shadow:none;
}
#rahm {
position:relative;
width:210px;
padding-left:100px;
padding-top:15px;
background-image:url('../img/rahm.png');
background-repeat:no-repeat;
background-position:center left;
float:left;
-webkit-hyphens:none;
-moz-hyphens:none;
hyphens:none;
}
#bubbles {
float:left;
background-image:url('../img/bubbles.png');
width:310px;
height:140px;
background-repeat:no-repeat;
background-position:18px 0px;
margin-top:15px;
span {
color:#000;
display:block;
width:100px;
&.lt {
float:left;
margin-left:34px;
margin-top:12px;
a {
color:#3B5998;
}
}
&.rt {
margin-top:52px;
margin-right:7px;
float:right;
font-size:24px;
line-height:20px;
}
}
}
}

Просмотреть файл

@ -319,6 +319,9 @@ test('api.middleware(method)', function(t) {
});
/* Implicitly testing the wrapped request methods just by
testing get, which is a bit lame but quicker. */
/* TODO: also test the underlying api.remote()? */
test('api.get', function(t) {
var api = new Api(ORIGIN);
@ -333,6 +336,21 @@ test('api.get', function(t) {
t.end();
});
t.test('passes optional params through', function(t) {
var requestMock = sinon.mock(request);
var get = requestMock.expects('get');
api.get('/foo', { some: 'params' }, function(){});
t.ok(get.calledOnce, 'called');
console.log(get.args);
t.ok(get.calledWith(
sinon.match(ORIGIN + '/foo'),
sinon.match({ some: 'params' })
), 'with params too');
requestMock.restore();
t.end();
});
t.test('leading slashes don\'t indicate absolute path', function(t) {
const WITH_PATH = 'http://example.org/base/';
var api = new Api(WITH_PATH);
@ -363,7 +381,7 @@ test('api.get', function(t) {
t.test('calls callback with 500 if request.get errors', function(t) {
var requestMock = sinon.mock(request);
var get = requestMock.expects('get').callsArgWith(1, 'Error');
var get = requestMock.expects('get').callsArgWith(2, 'Error');
api.get('/foo', function(err, data){
t.similar(err, { code: 500, name: 'Internal', message: 'Error' }, 'error');
@ -375,7 +393,7 @@ test('api.get', function(t) {
t.test('calls callback with 500 if request.get response is not 200', function(t) {
var requestMock = sinon.mock(request);
var get = requestMock.expects('get').callsArgWith(1, null, { statusCode: 404 });
var get = requestMock.expects('get').callsArgWith(2, null, { statusCode: 404 });
api.get('/foo', function(err, data){
t.similar(err, { code: 404, name: 'NotFound' }, 'error');
@ -388,7 +406,7 @@ test('api.get', function(t) {
t.test('calls callback with 500 if request.get response is not json', function(t) {
var requestMock = sinon.mock(request);
var get = requestMock.expects('get')
.callsArgWith(1, null, { statusCode: 200 }, "NOPE!");
.callsArgWith(2, null, { statusCode: 200 }, "NOPE!");
api.get('/foo', function(err, data){
t.similar(err, { code: 500, name: 'Internal', message: 'Unexpected token N' }, 'error');
@ -406,7 +424,7 @@ test('api.get', function(t) {
var requestMock = sinon.mock(request);
var get = requestMock.expects('get')
.callsArgWith(1, null, { statusCode: 200 }, JSON.stringify(response));
.callsArgWith(2, null, { statusCode: 200 }, JSON.stringify(response));
api.get('/foo', function(err, data){
t.similar(err, { code: 500, name: 'Internal', message: 'It broke.' }, 'error');
@ -416,7 +434,7 @@ test('api.get', function(t) {
});
});
t.test('successful call passes data through', function(t) {
t.test('stringified JSON data gets parsed', function(t) {
var response = {
status: 'ok',
data: 'Stuff.'
@ -424,7 +442,26 @@ test('api.get', function(t) {
var requestMock = sinon.mock(request);
var get = requestMock.expects('get')
.callsArgWith(1, null, { statusCode: 200 }, JSON.stringify(response));
.callsArgWith(2, null, { statusCode: 200 }, JSON.stringify(response));
api.get('/foo', function(err, data){
t.notOk(err, 'no error');
t.same(data, { status: 'ok', data: 'Stuff.' }, 'data');
requestMock.restore();
t.end();
});
});
/* request parses for you if you post with { json: ... } */
t.test('pre-parsed data is passed through', function(t) {
var response = {
status: 'ok',
data: 'Stuff.'
};
var requestMock = sinon.mock(request);
var get = requestMock.expects('get')
.callsArgWith(2, null, { statusCode: 200 }, response);
api.get('/foo', function(err, data){
t.notOk(err, 'no error');

Просмотреть файл

@ -77,6 +77,10 @@ const DATA = {
url: "http://issuer-b.org"
}
}
},
'claim': {
status: 'ok',
url: 'http://some-url.org/assertion'
}
};
@ -249,3 +253,23 @@ test('getIssuers', function(t) {
});
});
test('claim', function(t) {
t.test('with data', function(t) {
var postStub = mock.expects('post');
postStub.callsArgWith(2, null, DATA['claim']);
openbadger.claim({
code: 'CLAIMCODE',
email: 'EMAIL'
}, function(err, data) {
t.notOk(err, 'no error');
var opts = postStub.args[0][1];
t.ok(opts.json, 'post with json data');
t.ok(opts.json.auth, 'contains auth');
t.similar(opts.json, { email: 'EMAIL', code: 'CLAIMCODE' }, 'params');
t.end();
});
});
});

Просмотреть файл

@ -1,27 +0,0 @@
const $ = require('./');
const test = require('tap').test;
const Organization = require('../models/organization');
$.prepare([
{ model: 'Organization',
name: 'wbez',
values: {
id: 10,
name: 'WBEZ 91.5',
description: 'Chicago Public Radio',
url: 'http://www.wbez.org/',
imageUrl: 'https://twimg0-a.akamaihd.net/profile_images/858641792/WBEZ915_LOGO.jpg',
address: '848 East Grand Ave, Navy Pier, Chicago, Illinois 60611',
phone: '312.948.4600',
email: 'admin@wbez.org'
}
}
], function (fixtures) {
test('Finding an organization', function (t) {
const expect = fixtures['wbez'];
Organization.find(expect.id).success(function (instance) {
t.same(instance.rawAttributes, expect.rawAttributes);
t.end();
});
});
});

Просмотреть файл

@ -8,7 +8,8 @@
<img src="/media/images/badge-large.png">
</div>
<div class="span8">
<p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>To claim the badge you've earned, enter the "claim code" written on the paper badge you received from your teacher, counselor or mentor.</p>
<p>Then hit the "Claim This Badge" button on your screen. Once you do this, the badge will be added to your dashboard and you can throw the piece of paper away (better yet, recycle it!).</p>
<form method="get" class="form-horizontal well">
<fieldset>
<div class="control-group">
@ -19,7 +20,7 @@
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn">asdf</button>
<button type="submit" class="btn">Claim this badge</button>
</div>
</div>
</fieldset>

Просмотреть файл

@ -7,12 +7,15 @@
<img src="{{ badge.image }}">
</div>
<div class="span8">
<h3>What is this badge about?</h3>
<p>{{ badge.description }}</p>
<h3>How can you earn it?</h3>
<p class="text-right">
<a href="{{ badge.url }}/claim" class="btn">Claim this badge</a>
<a href="#applyModal" data-toggle="modal" class="btn">Apply</a>
<a href="{{ badge.url }}/favorite" class="btn"><i class="icon-heart"></i> Add to your favorites</a>
<a href="{{ badge.url }}/favorite" class="btn show-tooltip" title="Save this badge to your favorites"><i class="icon-heart"></i></a>
</p>
</div>
</div>
@ -27,7 +30,7 @@
<p>Badge blah in voluptate velit...</p>
<p class="text-right">
<a href="/badges/ae784f" class="btn">Details</a>
<a class="btn show-tooltip" title="Add to your favorites" href="/badges/ae784f/favorite"><i class="icon-heart"></i></a>
<a class="btn show-tooltip" title="Save this badge to your favorites" href="/badges/ae784f/favorite"><i class="icon-heart"></i></a>
</p>
</figcaption>
</figure>
@ -39,7 +42,7 @@
<p>Badge blah in voluptate velit...</p>
<p class="text-right">
<a href="/badges/ae784f" class="btn">Details</a>
<a class="btn show-tooltip" title="Add to your favorites" href="/badges/ae784f/favorite"><i class="icon-heart"></i></a>
<a class="btn show-tooltip" title="Save this badge to your favorites" href="/badges/ae784f/favorite"><i class="icon-heart"></i></a>
</p>
</figcaption>
</figure>
@ -51,7 +54,7 @@
<p>Badge blah in voluptate velit...</p>
<p class="text-right">
<a href="/badges/ae784f" class="btn">Details</a>
<a class="btn show-tooltip" title="Add to your favorites" href="/badges/ae784f/favorite"><i class="icon-heart"></i></a>
<a class="btn show-tooltip" title="Save this badge to your favorites" href="/badges/ae784f/favorite"><i class="icon-heart"></i></a>
</p>
</figcaption>
</figure>
@ -63,43 +66,113 @@
<p>Badge blah in voluptate velit...</p>
<p class="text-right">
<a href="/badges/ae784f" class="btn">Details</a>
<a class="btn show-tooltip" title="Add to your favorites" href="/badges/ae784f/favorite"><i class="icon-heart"></i></a>
<a class="btn show-tooltip" title="Save this badge to your favorites" href="/badges/ae784f/favorite"><i class="icon-heart"></i></a>
</p>
</figcaption>
</figure>
</li>
</ul>
{% endblock %}
{% block modal %}
<!-- Modal -->
<div id="applyModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="myModalLabel">Apply for this badge</h3>
<h3 id="myModalLabel">Apply for the {{badge.name}} badge</h3>
</div>
<div class="modal-body">
<h4>Before submitting your work check out the requirements for this badge:</h4>
{% if badge.prerequisites|length > 0 %}
<h4>In order to earn this badge you need to make sure you have met the following requirements:</h4>
<ul>
{% for req in badge.prerequisites %}
<li>{{req}}</li>
{% endfor %}
</ul>
{% endif %}
<p>Ac dignissim turpis mattis tristique in, nunc non. Magna amet diam odio hac ultrices? Mus! Adipiscing pulvinar elementum, cum arcu, sagittis magnis sit nascetur, habitasse amet nascetur, vel sociis non placerat, tortor sit etiam? Cras, nisi? Magna porttitor turpis, nec? Rhoncus magnis parturient dictumst ac pulvinar cursus aliquam, ac scelerisque. Velit tincidunt urna magna pulvinar velit, urna. Purus turpis nunc, non tincidunt augue proin montes non! Nascetur turpis cum mauris, mus elit. Porta in natoque quis est sociis nascetur turpis, augue amet egestas egestas, ut, in penatibus urna aliquam aenean, integer egestas velit tempor augue, enim mattis mattis amet, nec. Aliquam amet nisi et, augue sagittis porttitor nisi nec nascetur urna sit! Pulvinar adipiscing vel scelerisque sociis ac sed mauris.</p>
<h4>Start by uploading photos and videos of your work</h4>
<h4>Upload photos or videos to show your work</h4>
<div id="draggable-upload-area">
Drag and Drop Photos and Videos here or
<a href="#" class="btn btn-primary btn-block">Choose Photos and Videos to Upload</a>
</div><!-- .draggable-upload-area -->
<h4>Tell us more about your work</h4>
<h4>Tell us more about the work you uploaded</h4>
<p>Describe how your work meets the badge requirements and why you think you deserve to earn the badge. If you're especially proud of something you did, here's your chance to let us know.</p>
<textarea>Some helper text</textarea>
<a href="#" class="btn btn-primary btn-block">Apply</a>
<a href="#applySuccess" class="btn btn-primary btn-block" data-dismiss="modal" data-toggle="modal">Apply</a>
</div>
</div>
<!-- Modal -->
<div id="applySuccess" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3>Badge submission success!</h3>
</div>
<div class="modal-body">
{% if user.underage %}
<p>You are trying to apply for the {{badge.name}} badge. We just want to make sure your parent or guardian is OK with this. We've sent an email to your parent/guardian and as soon as they give their approval of your badge application, it will be sent to a mentor.</p>
<p>The mentor will then review your application and get back to you about your badge.</p>
<p>In the mean time, keep on learning, earning and leveling up</p>
{% else %}
<p>You have applied for the {{badge.name}} badge. Your application will be sent to a mentor who will review it and get back to you about your badge.</p>
<p>In the mean time, check out these badges and programs and keep on learning, earning and leveling up!</p>
{% endif %}
<ul class="thumbnails">
<li class="span3">
<figure class="thumbnail">
<a href="/badges/ae784f"><img src="/media/images/badge.png"></a>
<figcaption class="caption">
<p>Badge blah in voluptate velit...</p>
<p class="text-right">
<a href="/badges/ae784f" class="btn">Details</a>
<a class="btn show-tooltip" title="Save this badge to your favorites" href="/badges/ae784f/favorite"><i class="icon-heart"></i></a>
</p>
</figcaption>
</figure>
</li>
<li class="span3">
<figure class="thumbnail">
<a href="/badges/ae784f"><img src="/media/images/badge.png"></a>
<figcaption class="caption">
<p>Badge blah in voluptate velit...</p>
<p class="text-right">
<a href="/badges/ae784f" class="btn">Details</a>
<a class="btn show-tooltip" title="Save this badge to your favorites" href="/badges/ae784f/favorite"><i class="icon-heart"></i></a>
</p>
</figcaption>
</figure>
</li>
<li class="span3">
<figure class="thumbnail">
<a href="/badges/ae784f"><img src="/media/images/badge.png"></a>
<figcaption class="caption">
<p>Badge blah in voluptate velit...</p>
<p class="text-right">
<a href="/badges/ae784f" class="btn">Details</a>
<a class="btn show-tooltip" title="Save this badge to your favorites" href="/badges/ae784f/favorite"><i class="icon-heart"></i></a>
</p>
</figcaption>
</figure>
</li>
<li class="span3">
<figure class="thumbnail">
<a href="/badges/ae784f"><img src="/media/images/badge.png"></a>
<figcaption class="caption">
<p>Badge blah in voluptate velit...</p>
<p class="text-right">
<a href="/badges/ae784f" class="btn">Details</a>
<a class="btn show-tooltip" title="Save this badge to your favorites" href="/badges/ae784f/favorite"><i class="icon-heart"></i></a>
</p>
</figcaption>
</figure>
</li>
</ul>
</div>
</div>
{% endblock %}

Просмотреть файл

@ -21,7 +21,7 @@
<div class="nav-wrap">
<ul class="nav">
<li class="learn{% if navItem == 'learn' %} active{% endif %}">
<a href="/learn">Learn</a>
<a href="/learn">Explore</a>
</li>
<li class="badges{% if navItem == 'badges' %} active{% endif %}">
<a href="/earn">Earn</a>
@ -118,6 +118,19 @@
<div class="inner-wrapper">
<div class="container">
<div class="upper clearfix">
<p class="pull-left">
More than 100 organizations across the city have joined forces to integrate learning into summer activities for youth of all ages in every Chicago neighborhood.
<br><br>
With your help, we'll launch the largest citywide summer learning campaign in the nation.
</p>
<ul class="pull-right">
<li><a class="logo chi" href="#">City of Chicago</a></li>
<li><a class="logo moz" href="#">Mozilla</a></li>
<li><a class="logo mac" href="#">MacArthur Foundation</a></li>
</ul>
</div>
<div class="lower clearfix">
<ul class="pull-left">
<li><a href="mailto:summeroflearning@cityofchicago.org">summeroflearning@cityofchicago.org</a></li>
<li><span>&copy; 2013</span></li>
@ -129,18 +142,6 @@
<li><a href="/vpat">VPAT</a></li>
</ul>
</div>
<div class="lower clearfix">
<p class="pull-left">
More than 100 organizations across the city have joined forces to integrate learning into summer activities for youth of all ages in every Chicago neighborhood.
<br><br>
With your help, we'll launch the largest citywide summer learning campaign in the nation.
</p>
<ul class="pull-right">
<li><a class="logo chi" href="#">City of Chicago</a></li>
<li><a class="logo mac" href="#">MacArthur Foundation</a></li>
<li><a class="logo moz" href="#">Mozilla</a></li>
</ul>
</div>
</div>
</div>
</div>